Add searching for member with scout
continuous-integration/drone/push Build is failing Details

This commit is contained in:
philipp lang 2023-02-26 20:51:59 +01:00
parent afa29b416b
commit cba65ce640
16 changed files with 586 additions and 128 deletions

View File

@ -4,8 +4,6 @@ namespace App\Contribution\Actions;
use App\Contribution\ContributionFactory;
use App\Country;
use App\Member\Member;
use App\Member\MemberResource;
use Inertia\Inertia;
use Inertia\Response;
use Lorisleiva\Actions\Concerns\AsAction;
@ -20,9 +18,10 @@ class FormAction
public function handle(): array
{
return [
'allMembers' => MemberResource::collection(Member::slangOrdered()->get()),
'countries' => Country::pluck('name', 'id'),
'defaultCountry' => Country::firstWhere('name', 'Deutschland')->id,
'countries' => Country::select('name', 'id')->get(),
'data' => [
'country' => Country::firstWhere('name', 'Deutschland')->id,
],
'compilers' => app(ContributionFactory::class)->compilerSelect(),
];
}

View File

@ -19,13 +19,12 @@ class MemberView
$activities = Activity::with('subactivities')->get();
return [
'data' => MemberResource::collection(Member::select('*')
->filter($filter)->search($request->query('search', null))
'data' => MemberResource::collection(Member::search($request->search)->query(fn ($q) => $q->select('*')
->filter($filter)
->with('payments.subscription')->with('memberships')->with('courses')->with('subscription')->with('leaderMemberships')->with('ageGroupMemberships')
->withPendingPayment()
->orderByRaw('lastname, firstname')
->paginate(15)
),
->ordered()
)->paginate(15)),
'filterActivities' => Activity::where('is_filterable', true)->pluck('name', 'id'),
'filterSubactivities' => Subactivity::where('is_filterable', true)->pluck('name', 'id'),
'toolbar' => [['href' => route('member.index'), 'label' => 'Zurück', 'color' => 'primary', 'icon' => 'plus']],

View File

@ -0,0 +1,40 @@
<?php
namespace App\Member\Actions;
use App\Member\Member;
use App\Member\MemberResource;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class SearchAction
{
use AsAction;
/**
* @return Collection<Member>
*/
public function handle(string $search): Collection
{
return Member::search($search)->query(fn ($query) => $query->ordered())->get();
}
public function asController(ActionRequest $request): AnonymousResourceCollection
{
if ($request->input('minLength') !== null && strlen($request->input('search')) < $request->input('minLength')) {
return MemberResource::collection($this->empty());
}
return MemberResource::collection($this->handle($request->input('search', '')));
}
/**
* @return Collection<Member>
*/
private function empty(): Collection
{
return Member::where('id', -1)->get();
}
}

View File

@ -23,9 +23,11 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Notifications\Notifiable;
use Laravel\Scout\Attributes\SearchUsingFullText;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader;
use Spatie\LaravelData\Lazy;
use Laravel\Scout\Searchable;
/**
* @property string $subscription_name
@ -37,6 +39,7 @@ class Member extends Model
use HasNamiField;
use HasFactory;
use Sluggable;
use Searchable;
/**
* @var array<string, string>
@ -46,7 +49,7 @@ class Member extends Model
/**
* @var array<int, string>
*/
public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug'];
public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug', 'search'];
/**
* @var array<int, string>
@ -83,23 +86,6 @@ class Member extends Model
];
}
/**
* @param Builder<self> $query
* @return Builder<self>
*/
public function scopeSearch(Builder $query, ?string $text): Builder
{
if (is_null($text)) {
return $query;
}
return $query->where('firstname', 'LIKE', '%'.$text.'%')
->orWhere('lastname', 'LIKE', '%'.$text.'%')
->orWhere('address', 'LIKE', '%'.$text.'%')
->orWhere('zip', 'LIKE', '%'.$text.'%')
->orWhere('location', 'LIKE', '%'.$text.'%');
}
// ---------------------------------- Actions ----------------------------------
public function syncVersion(): void
{
@ -289,6 +275,8 @@ class Member extends Model
$model->payments->each->delete();
$model->memberships->each->delete();
});
static::saving(fn ($model) => $model->updateSearch());
}
// ---------------------------------- Scopes -----------------------------------
@ -301,15 +289,6 @@ class Member extends Model
return $query->orderByRaw('lastname, firstname');
}
/**
* @param Builder<self> $query
* @return Builder<self>
*/
public function scopeSlangOrdered(Builder $query): Builder
{
return $query->orderByRaw('firstname, lastname');
}
/**
* @param Builder<self> $query
* @return Builder<self>
@ -496,4 +475,23 @@ class Member extends Model
'mglnr' => Lazy::create(fn () => 'Mglnr.: '.$this->nami_id),
]);
}
/**
* @return array<string, mixed>
*/
#[SearchUsingFullText(['search_text'])]
public function toSearchableArray(): array
{
return [
'search_text' => $this->search_text ?: '',
];
}
public function updateSearch(): void
{
$this->attributes['search_text'] = collect([
$this->fullname,
$this->fullAddress,
])->implode(' ');
}
}

View File

@ -32,6 +32,7 @@
"inertiajs/inertia-laravel": "^0.2.5",
"laravel/framework": "^9.0",
"laravel/horizon": "^5.0",
"laravel/scout": "^9.8",
"laravel/telescope": "^4.13",
"laravel/tinker": "^2.0",
"laravel/ui": "^3.0",

165
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b8d71a8e1d598702057d6ecdaeabd3e2",
"content-hash": "f43fe5529405b4aeb18cdac45386f4f6",
"packages": [
{
"name": "amphp/amp",
@ -1748,24 +1748,24 @@
},
{
"name": "graham-campbell/result-type",
"version": "v1.1.0",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
"reference": "a878d45c1914464426dc94da61c9e1d36ae262a8"
"reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8",
"reference": "a878d45c1914464426dc94da61c9e1d36ae262a8",
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
"reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"phpoption/phpoption": "^1.9"
"phpoption/phpoption": "^1.9.1"
},
"require-dev": {
"phpunit/phpunit": "^8.5.28 || ^9.5.21"
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
},
"type": "library",
"autoload": {
@ -1794,7 +1794,7 @@
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0"
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1"
},
"funding": [
{
@ -1806,7 +1806,7 @@
"type": "tidelift"
}
],
"time": "2022-07-30T15:56:11+00:00"
"time": "2023-02-25T20:23:15+00:00"
},
{
"name": "guzzlehttp/guzzle",
@ -2637,6 +2637,79 @@
},
"time": "2023-02-10T15:40:38+00:00"
},
{
"name": "laravel/scout",
"version": "v9.8.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/scout.git",
"reference": "38595717b396ce733d432b82e3225fa4e0d6c8ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/scout/zipball/38595717b396ce733d432b82e3225fa4e0d6c8ef",
"reference": "38595717b396ce733d432b82e3225fa4e0d6c8ef",
"shasum": ""
},
"require": {
"illuminate/bus": "^8.0|^9.0|^10.0",
"illuminate/contracts": "^8.0|^9.0|^10.0",
"illuminate/database": "^8.0|^9.0|^10.0",
"illuminate/http": "^8.0|^9.0|^10.0",
"illuminate/pagination": "^8.0|^9.0|^10.0",
"illuminate/queue": "^8.0|^9.0|^10.0",
"illuminate/support": "^8.0|^9.0|^10.0",
"php": "^7.3|^8.0"
},
"require-dev": {
"meilisearch/meilisearch-php": "^0.19",
"mockery/mockery": "^1.0",
"orchestra/testbench": "^6.17|^7.0|^8.0",
"php-http/guzzle7-adapter": "^1.0",
"phpunit/phpunit": "^9.3"
},
"suggest": {
"algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).",
"meilisearch/meilisearch-php": "Required to use the MeiliSearch engine (^0.23)."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Scout\\ScoutServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Scout\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Scout provides a driver based solution to searching your Eloquent models.",
"keywords": [
"algolia",
"laravel",
"search"
],
"support": {
"issues": "https://github.com/laravel/scout/issues",
"source": "https://github.com/laravel/scout"
},
"time": "2023-02-14T16:53:14+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v1.3.0",
@ -4420,24 +4493,24 @@
},
{
"name": "phpoption/phpoption",
"version": "1.9.0",
"version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
"reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab"
"reference": "dd3a383e599f49777d8b628dadbb90cae435b87e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab",
"reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab",
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e",
"reference": "dd3a383e599f49777d8b628dadbb90cae435b87e",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8",
"phpunit/phpunit": "^8.5.28 || ^9.5.21"
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
},
"type": "library",
"extra": {
@ -4479,7 +4552,7 @@
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
"source": "https://github.com/schmittjoh/php-option/tree/1.9.0"
"source": "https://github.com/schmittjoh/php-option/tree/1.9.1"
},
"funding": [
{
@ -4491,7 +4564,7 @@
"type": "tidelift"
}
],
"time": "2022-07-30T15:51:26+00:00"
"time": "2023-02-25T19:38:58+00:00"
},
{
"name": "psr/cache",
@ -9563,7 +9636,7 @@
"dist": {
"type": "path",
"url": "./packages/tex",
"reference": "48251272de62e3fea044a7ad31e1a411c15eb4c6"
"reference": "6f162102ef7ceca41822d18c3e694abd926f550b"
},
"type": "library",
"extra": {
@ -10000,23 +10073,23 @@
},
{
"name": "orchestra/testbench",
"version": "v7.22.0",
"version": "v7.22.1",
"source": {
"type": "git",
"url": "https://github.com/orchestral/testbench.git",
"reference": "4e16b1adde9d13aff6e4221c841bb82dcd4416f0"
"reference": "987f5383f597a54f8d9868f98411899398348c02"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/orchestral/testbench/zipball/4e16b1adde9d13aff6e4221c841bb82dcd4416f0",
"reference": "4e16b1adde9d13aff6e4221c841bb82dcd4416f0",
"url": "https://api.github.com/repos/orchestral/testbench/zipball/987f5383f597a54f8d9868f98411899398348c02",
"reference": "987f5383f597a54f8d9868f98411899398348c02",
"shasum": ""
},
"require": {
"fakerphp/faker": "^1.21",
"laravel/framework": "^9.50.2",
"laravel/framework": "^9.52.4",
"mockery/mockery": "^1.5.1",
"orchestra/testbench-core": "^7.22",
"orchestra/testbench-core": "^7.22.1",
"php": "^8.0",
"phpunit/phpunit": "^9.5.10",
"spatie/laravel-ray": "^1.28",
@ -10053,22 +10126,22 @@
],
"support": {
"issues": "https://github.com/orchestral/testbench/issues",
"source": "https://github.com/orchestral/testbench/tree/v7.22.0"
"source": "https://github.com/orchestral/testbench/tree/v7.22.1"
},
"time": "2023-02-08T02:31:25+00:00"
"time": "2023-02-24T01:07:22+00:00"
},
{
"name": "orchestra/testbench-core",
"version": "v7.22.0",
"version": "v7.22.1",
"source": {
"type": "git",
"url": "https://github.com/orchestral/testbench-core.git",
"reference": "a6e4240149ffc1bb3068a68fe4261a5b59753079"
"reference": "75b42f9130e7903ffa0ef8dbb466962ca6635261"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/orchestral/testbench-core/zipball/a6e4240149ffc1bb3068a68fe4261a5b59753079",
"reference": "a6e4240149ffc1bb3068a68fe4261a5b59753079",
"url": "https://api.github.com/repos/orchestral/testbench-core/zipball/75b42f9130e7903ffa0ef8dbb466962ca6635261",
"reference": "75b42f9130e7903ffa0ef8dbb466962ca6635261",
"shasum": ""
},
"require": {
@ -10076,7 +10149,7 @@
},
"require-dev": {
"fakerphp/faker": "^1.21",
"laravel/framework": "^9.50.2",
"laravel/framework": "^9.52.4",
"laravel/pint": "^1.4",
"mockery/mockery": "^1.5.1",
"orchestra/canvas": "^7.0",
@ -10090,7 +10163,7 @@
"suggest": {
"brianium/paratest": "Allow using parallel tresting (^6.4).",
"fakerphp/faker": "Allow using Faker for testing (^1.21).",
"laravel/framework": "Required for testing (^9.50.2).",
"laravel/framework": "Required for testing (^9.52.4).",
"mockery/mockery": "Allow using Mockery for testing (^1.5.1).",
"nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^6.2).",
"orchestra/testbench-browser-kit": "Allow using legacy Laravel BrowserKit for testing (^7.0).",
@ -10141,7 +10214,7 @@
"issues": "https://github.com/orchestral/testbench/issues",
"source": "https://github.com/orchestral/testbench-core"
},
"time": "2023-02-08T02:19:30+00:00"
"time": "2023-02-23T12:15:23+00:00"
},
{
"name": "phar-io/manifest",
@ -10343,16 +10416,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.1",
"version": "1.10.3",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "1cd5fc530a8b68702f3733ad64294b2a39564198"
"reference": "5419375b5891add97dc74be71e6c1c34baaddf64"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/1cd5fc530a8b68702f3733ad64294b2a39564198",
"reference": "1cd5fc530a8b68702f3733ad64294b2a39564198",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5419375b5891add97dc74be71e6c1c34baaddf64",
"reference": "5419375b5891add97dc74be71e6c1c34baaddf64",
"shasum": ""
},
"require": {
@ -10382,7 +10455,7 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
"source": "https://github.com/phpstan/phpstan/tree/1.10.1"
"source": "https://github.com/phpstan/phpstan/tree/1.10.3"
},
"funding": [
{
@ -10398,27 +10471,27 @@
"type": "tidelift"
}
],
"time": "2023-02-21T21:57:23+00:00"
"time": "2023-02-25T14:47:13+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.24",
"version": "9.2.25",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "2cf940ebc6355a9d430462811b5aaa308b174bed"
"reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed",
"reference": "2cf940ebc6355a9d430462811b5aaa308b174bed",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954",
"reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.14",
"nikic/php-parser": "^4.15",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -10467,7 +10540,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25"
},
"funding": [
{
@ -10475,7 +10548,7 @@
"type": "github"
}
],
"time": "2023-01-26T08:26:55+00:00"
"time": "2023-02-25T05:32:00+00:00"
},
{
"name": "phpunit/php-file-iterator",

142
config/scout.php Normal file
View File

@ -0,0 +1,142 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Search Engine
|--------------------------------------------------------------------------
|
| This option controls the default search connection that gets used while
| using Laravel Scout. This connection is used when syncing all models
| to the search service. You should adjust this based on your needs.
|
| Supported: "algolia", "meilisearch", "database", "collection", "null"
|
*/
'driver' => env('SCOUT_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Index Prefix
|--------------------------------------------------------------------------
|
| Here you may specify a prefix that will be applied to all search index
| names used by Scout. This prefix may be useful if you have multiple
| "tenants" or applications sharing the same search infrastructure.
|
*/
'prefix' => env('SCOUT_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Queue Data Syncing
|--------------------------------------------------------------------------
|
| This option allows you to control if the operations that sync your data
| with your search engines are queued. When this is set to "true" then
| all automatic data syncing will get queued for better performance.
|
*/
'queue' => env('SCOUT_QUEUE', false),
/*
|--------------------------------------------------------------------------
| Database Transactions
|--------------------------------------------------------------------------
|
| This configuration option determines if your data will only be synced
| with your search indexes after every open database transaction has
| been committed, thus preventing any discarded data from syncing.
|
*/
'after_commit' => false,
/*
|--------------------------------------------------------------------------
| Chunk Sizes
|--------------------------------------------------------------------------
|
| These options allow you to control the maximum chunk size when you are
| mass importing data into the search engine. This allows you to fine
| tune each of these chunk sizes based on the power of the servers.
|
*/
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
/*
|--------------------------------------------------------------------------
| Soft Deletes
|--------------------------------------------------------------------------
|
| This option allows to control whether to keep soft deleted records in
| the search indexes. Maintaining soft deleted records can be useful
| if your application still needs to search for the records later.
|
*/
'soft_delete' => false,
/*
|--------------------------------------------------------------------------
| Identify User
|--------------------------------------------------------------------------
|
| This option allows you to control whether to notify the search engine
| of the user performing the search. This is sometimes useful if the
| engine supports any analytics based on this application's users.
|
| Supported engines: "algolia"
|
*/
'identify' => env('SCOUT_IDENTIFY', false),
/*
|--------------------------------------------------------------------------
| Algolia Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Algolia settings. Algolia is a cloud hosted
| search engine which works great with Scout out of the box. Just plug
| in your application ID and admin API key to get started searching.
|
*/
'algolia' => [
'id' => env('ALGOLIA_APP_ID', ''),
'secret' => env('ALGOLIA_SECRET', ''),
],
/*
|--------------------------------------------------------------------------
| MeiliSearch Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your MeiliSearch settings. MeiliSearch is an open
| source search engine with minimal configuration. Below, you can state
| the host and key information for your own MeiliSearch installation.
|
| See: https://docs.meilisearch.com/guides/advanced_guides/configuration.html
|
*/
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [
// 'users' => [
// 'filterableAttributes'=> ['id', 'name', 'email'],
// ],
],
],
];

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('members', function (Blueprint $table) {
$table->text('search_text')->default('');
$table->fullText('search_text');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('members', function (Blueprint $table) {
$table->dropColumn('search_text');
});
}
};

156
package-lock.json generated
View File

@ -18,12 +18,14 @@
},
"devDependencies": {
"autoprefixer": "latest",
"axios": "^1.3.4",
"eslint": "^8.9.0",
"eslint-plugin-vue": "^8.4.1",
"laravel-mix": "^6.0.1",
"postcss": "^8.4.6",
"tailwindcss": "^3.0.19",
"vue": "^2.6",
"vue-axios": "^3.5.2",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14"
}
@ -1836,6 +1838,14 @@
"vue": "^2.6.0"
}
},
"node_modules/@inertiajs/inertia/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz",
@ -2955,6 +2965,12 @@
"lodash": "^4.17.14"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
},
"node_modules/autoprefixer": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz",
@ -2983,11 +2999,14 @@
}
},
"node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.14.0"
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/babel-loader": {
@ -3724,6 +3743,18 @@
"text-hex": "1.0.x"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@ -4403,6 +4434,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@ -5428,9 +5468,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/follow-redirects": {
"version": "1.14.8",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
@ -5446,6 +5486,20 @@
}
}
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@ -8494,6 +8548,12 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
"node_modules/pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@ -10134,6 +10194,16 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
},
"node_modules/vue-axios": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.5.2.tgz",
"integrity": "sha512-GP+dct7UlAWkl1qoP3ppw0z6jcSua5/IrMpjB5O8bh089iIiJ+hdxPYH2NPEpajlYgkW5EVMP95ttXWdas1O0g==",
"dev": true,
"peerDependencies": {
"axios": "*",
"vue": "^3.0.0 || ^2.0.0"
}
},
"node_modules/vue-eslint-parser": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.2.0.tgz",
@ -12170,6 +12240,16 @@
"axios": "^0.21.1",
"deepmerge": "^4.0.0",
"qs": "^6.9.0"
},
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
}
}
}
},
"@inertiajs/inertia-vue": {
@ -13091,6 +13171,12 @@
"lodash": "^4.17.14"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
},
"autoprefixer": {
"version": "10.4.2",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.2.tgz",
@ -13106,11 +13192,14 @@
}
},
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"dev": true,
"requires": {
"follow-redirects": "^1.14.0"
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"babel-loader": {
@ -13705,6 +13794,15 @@
"text-hex": "1.0.x"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
},
"commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@ -14229,6 +14327,12 @@
}
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@ -15037,9 +15141,20 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"follow-redirects": {
"version": "1.14.8",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA=="
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"forwarded": {
"version": "0.2.0",
@ -17240,6 +17355,12 @@
}
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@ -18514,6 +18635,13 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
},
"vue-axios": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/vue-axios/-/vue-axios-3.5.2.tgz",
"integrity": "sha512-GP+dct7UlAWkl1qoP3ppw0z6jcSua5/IrMpjB5O8bh089iIiJ+hdxPYH2NPEpajlYgkW5EVMP95ttXWdas1O0g==",
"dev": true,
"requires": {}
},
"vue-eslint-parser": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.2.0.tgz",

View File

@ -13,12 +13,14 @@
},
"devDependencies": {
"autoprefixer": "latest",
"axios": "^1.3.4",
"eslint": "^8.9.0",
"eslint-plugin-vue": "^8.4.1",
"laravel-mix": "^6.0.1",
"postcss": "^8.4.6",
"tailwindcss": "^3.0.19",
"vue": "^2.6",
"vue-axios": "^3.5.2",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14"
},

@ -1 +1 @@
Subproject commit 315be84cf38f1a01f890180099d3b2156bf344b9
Subproject commit 7b08f30cb2d44a74f59c1441f797ab544e753701

3
resources/js/app.js vendored
View File

@ -12,10 +12,13 @@ import AppLayout from './layouts/AppLayout.vue';
import VTooltip from 'v-tooltip';
import hasModule from './mixins/hasModule.js';
import PortalVue from 'portal-vue';
import axios from 'axios';
import VueAxios from 'vue-axios';
Vue.use(plugin);
Vue.use(PortalVue);
Vue.use(VTooltip);
Vue.use(VueAxios, axios);
Vue.component('f-text', () => import(/* webpackChunkName: "form" */ './components/FText'));
Vue.component('f-switch', () => import(/* webpackChunkName: "form" */ './components/FSwitch'));
Vue.component('f-select', () => import(/* webpackChunkName: "form" */ './components/FSelect'));

View File

@ -1,5 +1,5 @@
<template>
<form action="/contribution/generate" target="_BLANK" class="max-w-2xl w-full mx-auto gap-6 grid-cols-2 grid p-6">
<form action="/contribution/generate" target="_BLANK" class="max-w-4xl w-full mx-auto gap-6 grid-cols-2 grid p-6">
<f-text
id="eventName"
name="eventName"
@ -31,24 +31,26 @@
<div class="border-gray-200 shadow shadow-primary-700 p-3 shadow-[0_0_4px_gray] col-span-2">
<f-text
class="col-span-2"
id="membersearch"
name="membersearch"
v-model="membersearch"
id="search_text"
name="search_text"
v-model="searchText"
label="Suchen …"
size="sm"
ref="membersearchfield"
ref="search_text_field"
@keypress.enter.prevent="onSubmitFirstMemberResult"
></f-text>
<div class="mt-2 grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-2 col-span-2">
<div class="mt-2 grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-2 col-span-2">
<f-switch
:id="`members-${member.id}`"
:key="member.id"
:label="`${member.firstname} ${member.lastname}`"
v-for="member in memberResults"
v-for="member in search.results"
name="members[]"
:value="member.id"
v-model="values.members"
size="sm"
@keypress.enter.prevent="onSubmitMemberResult(member)"
inline
></f-switch>
</div>
</div>
@ -66,10 +68,15 @@
</template>
<script>
import debounce from 'lodash/debounce';
export default {
data: function () {
return {
membersearch: '',
search: {
s: '',
results: [],
},
values: {
members: [],
event_name: '',
@ -77,27 +84,27 @@ export default {
dateUntil: '',
zipLocation: '',
country: null,
...this.data,
},
};
},
props: {
data: {},
countries: {},
defaultCountry: {},
allMembers: {},
compilers: {},
},
computed: {
memberResults() {
if (this.membersearch.length === 0) {
return this.allMembers.data;
}
searchText: {
get() {
return this.search.s;
},
set: debounce(async function(event) {
this.search.s = event;
return this.allMembers.data.filter(
(member) =>
(member.firstname + ' ' + member.lastname)
.toLowerCase()
.indexOf(this.membersearch.toLowerCase()) !== -1
);
var response = await this.axios.post('/api/member/search', {search: event, minLength: 3});
this.search.results = response.data.data;
}, 300),
},
},
methods: {
@ -108,21 +115,18 @@ export default {
this.values.members.push(selected.id);
}
this.membersearch = '';
this.$refs.membersearchfield.$el.querySelector('input').focus();
this.searchText = '';
this.$refs.search_text_field.$el.querySelector('input').focus();
},
onSubmitFirstMemberResult() {
if (this.memberResults.length === 0) {
this.membersearch = '';
if (this.search.results.length === 0) {
this.searchText = '';
return;
}
this.onSubmitMemberResult(this.memberResults[0]);
this.onSubmitMemberResult(this.search.results[0]);
},
},
created() {
this.values.country = this.defaultCountry;
},
};
</script>

View File

@ -15,6 +15,7 @@ use App\Initialize\Actions\InitializeAction;
use App\Initialize\Actions\InitializeFormAction;
use App\Member\Actions\MemberResyncAction;
use App\Member\Actions\MemberShowAction;
use App\Member\Actions\SearchAction;
use App\Member\MemberController;
use App\Membership\Actions\MembershipDestroyAction;
use App\Membership\Actions\MembershipStoreAction;
@ -32,6 +33,7 @@ Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void {
Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/', HomeIndexAction::class)->name('home');
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');
Route::resource('member', MemberController::class)->except('show');

View File

@ -15,16 +15,22 @@ class IndexTest extends TestCase
public function testItHasContributionIndex(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
Country::factory()->create(['name' => 'Deutschland']);
$country = Country::factory()->create(['name' => 'Deutschland']);
Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
Member::factory()->defaults()->create(['firstname' => 'Jane', 'lastname' => 'Muster']);
$response = $this->get('/contribution');
$this->assertInertiaHas('Jane', $response, 'allMembers.data.0.firstname');
$this->assertInertiaHas([
'class' => DvDocument::class,
'title' => 'Für DV erstellen',
], $response, 'compilers.0');
$this->assertInertiaHas([
'id' => $country->id,
'name' => $country->name,
], $response, 'countries.0');
$this->assertInertiaHas([
'country' => $country->id,
], $response, 'data');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Tests\Feature\Member;
use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class SearchTest extends TestCase
{
use DatabaseTransactions;
public function testItHasSearchIndex(): void
{
$this->withoutExceptionHandling()->login();
$member = Member::factory()->defaults()->create([
'firstname' => '::firstname::',
'lastname' => '::lastname::',
'address' => 'Kölner Str 3',
'zip' => 33333,
'location' => 'Hilden',
]);
$this->assertEquals('::firstname:: ::lastname:: Kölner Str 3, 33333 Hilden', $member->search_text);
}
}