From 067cbe6d9d36020d34b3bd4dd91c3d34ef91d093 Mon Sep 17 00:00:00 2001 From: philipp lang Date: Thu, 23 Feb 2023 22:43:13 +0100 Subject: [PATCH] Add activity index filter --- app/Activity.php | 13 ++ app/Activity/Actions/IndexAction.php | 11 +- app/Activity/Resources/ActivityResource.php | 16 ++ app/Http/Views/ActivityFilterScope.php | 45 ++++++ app/Http/Views/Filter.php | 72 +++++++++ app/Lib/HasMeta.php | 33 ++++ resources/img/svg/activity.svg | 1 + resources/js/layouts/AppLayout.vue | 1 + resources/js/views/activity/VIndex.vue | 157 ++++++++++++++++++++ tests/Feature/Activity/IndexTest.php | 27 +++- tests/Lib/MakesHttpCalls.php | 28 ++++ tests/TestCase.php | 2 + 12 files changed, 400 insertions(+), 6 deletions(-) create mode 100644 app/Http/Views/ActivityFilterScope.php create mode 100644 app/Http/Views/Filter.php create mode 100644 app/Lib/HasMeta.php create mode 100644 resources/img/svg/activity.svg create mode 100644 resources/js/views/activity/VIndex.vue create mode 100644 tests/Lib/MakesHttpCalls.php diff --git a/app/Activity.php b/app/Activity.php index eab40f62..cb7ef443 100644 --- a/app/Activity.php +++ b/app/Activity.php @@ -2,8 +2,10 @@ namespace App; +use App\Http\Views\ActivityFilterScope; use App\Nami\HasNamiField; use Cviebrock\EloquentSluggable\Sluggable; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -30,6 +32,16 @@ class Activity extends Model ]; } + /** + * @param Builder $query + * + * @return Builder + */ + public function scopeWithFilter(Builder $query, ActivityFilterScope $filter): Builder + { + return $filter->apply($query); + } + /** * @return BelongsToMany */ @@ -37,4 +49,5 @@ class Activity extends Model { return $this->belongsToMany(Subactivity::class); } + } diff --git a/app/Activity/Actions/IndexAction.php b/app/Activity/Actions/IndexAction.php index d5cdfc84..54fb92f5 100644 --- a/app/Activity/Actions/IndexAction.php +++ b/app/Activity/Actions/IndexAction.php @@ -4,6 +4,7 @@ namespace App\Activity\Actions; use App\Activity; use App\Activity\Resources\ActivityResource; +use App\Http\Views\ActivityFilterScope; use Illuminate\Http\Resources\Json\AnonymousResourceCollection; use Inertia\Inertia; use Inertia\Response; @@ -14,15 +15,17 @@ class IndexAction { use AsAction; - public function handle(): AnonymousResourceCollection + public function handle(ActivityFilterScope $filter): AnonymousResourceCollection { - return ActivityResource::collection(Activity::local()->paginate(20)); + return ActivityResource::collection(Activity::local()->withFilter($filter)->paginate(20)); } public function asController(ActionRequest $request): Response { - return Inertia::render('activity/Index', [ - 'data' => $this->handle(), + $filter = ActivityFilterScope::fromRequest($request->input('filter')); + + return Inertia::render('activity/VIndex', [ + 'data' => $this->handle($filter), ]); } } diff --git a/app/Activity/Resources/ActivityResource.php b/app/Activity/Resources/ActivityResource.php index 1b341161..cb5a42ef 100644 --- a/app/Activity/Resources/ActivityResource.php +++ b/app/Activity/Resources/ActivityResource.php @@ -3,6 +3,9 @@ namespace App\Activity\Resources; use App\Activity; +use App\Http\Views\ActivityFilterScope; +use App\Lib\HasMeta; +use App\Subactivity; use Illuminate\Http\Resources\Json\JsonResource; /** @@ -10,6 +13,8 @@ use Illuminate\Http\Resources\Json\JsonResource; */ class ActivityResource extends JsonResource { + use HasMeta; + /** * Transform the resource into an array. * @@ -24,4 +29,15 @@ class ActivityResource extends JsonResource 'id' => $this->id, ]; } + + /** + * @return array + */ + public static function meta(): array + { + return [ + 'subactivities' => Subactivity::pluck('name', 'id'), + 'filter' => ActivityFilterScope::fromRequest(request()->input('filter')), + ]; + } } diff --git a/app/Http/Views/ActivityFilterScope.php b/app/Http/Views/ActivityFilterScope.php new file mode 100644 index 00000000..474f4e12 --- /dev/null +++ b/app/Http/Views/ActivityFilterScope.php @@ -0,0 +1,45 @@ + + */ +#[MapInputName(SnakeCaseMapper::class)] +#[MapOutputName(SnakeCaseMapper::class)] +class ActivityFilterScope extends Filter +{ + public function __construct( + public ?int $subactivityId = null, + ) { + } + + /** + * {@inheritdoc} + */ + public function locks(): array + { + return []; + } + + /** + * @param Builder $query + * + * @return Builder + */ + public function apply(Builder $query): Builder + { + if ($this->subactivityId) { + $query->whereHas('subactivities', fn ($query) => $query->where('id', $this->subactivityId)); + } + + return $query; + } +} + diff --git a/app/Http/Views/Filter.php b/app/Http/Views/Filter.php new file mode 100644 index 00000000..e713d0ef --- /dev/null +++ b/app/Http/Views/Filter.php @@ -0,0 +1,72 @@ + + */ + abstract protected function locks(): array; + + public static function fromRequest(?string $request = null): static + { + $parameters = json_decode(base64_decode($request), true); + + return static::from($parameters ?: [])->parseLocks(); + } + + public function parseLocks(): static + { + foreach ($this->locks() as $key => $value) { + if ($value === $this->unsetReplacer) { + continue; + } + + $this->{$key} = $value; + } + + return $this; + } + + /** + * @param mixed $value + * + * @return mixed + */ + public function when(bool $when, $value) + { + return $when ? $value : $this->unsetReplacer; + } + + /** + * @param Builder $query + * + * @return Builder + */ + protected function applyOwnOthers(Builder $query, bool $own, bool $others): Builder + { + if ($own && !$others) { + $query->where('user_id', auth()->id()); + } + + if (!$own && $others) { + $query->where('user_id', '!=', auth()->id()); + } + + if (!$own && !$others) { + $query->where('id', -1); + } + + return $query; + } +} diff --git a/app/Lib/HasMeta.php b/app/Lib/HasMeta.php new file mode 100644 index 00000000..871d2610 --- /dev/null +++ b/app/Lib/HasMeta.php @@ -0,0 +1,33 @@ +additional([ + 'meta' => $meta, + ]); + } + + public static function meta(): array + { + return []; + } +} + diff --git a/resources/img/svg/activity.svg b/resources/img/svg/activity.svg new file mode 100644 index 00000000..f7cc0252 --- /dev/null +++ b/resources/img/svg/activity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/layouts/AppLayout.vue b/resources/js/layouts/AppLayout.vue index 2f479436..8e3dfdbd 100644 --- a/resources/js/layouts/AppLayout.vue +++ b/resources/js/layouts/AppLayout.vue @@ -17,6 +17,7 @@ >Beiträge Zuschüsse + Tätigkeiten
Einstellungen diff --git a/resources/js/views/activity/VIndex.vue b/resources/js/views/activity/VIndex.vue new file mode 100644 index 00000000..d8b23709 --- /dev/null +++ b/resources/js/views/activity/VIndex.vue @@ -0,0 +1,157 @@ + + + diff --git a/tests/Feature/Activity/IndexTest.php b/tests/Feature/Activity/IndexTest.php index 3149c155..a6280619 100644 --- a/tests/Feature/Activity/IndexTest.php +++ b/tests/Feature/Activity/IndexTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Activity; use App\Activity; +use App\Subactivity; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\TestCase; @@ -13,12 +14,34 @@ class IndexTest extends TestCase public function testItDisplaysLocalActivities(): void { $this->login()->loginNami()->withoutExceptionHandling(); - $local = Activity::factory()->name('Local')->create(); - $remote = Activity::factory()->name('Remote')->inNami(123)->create(); + Activity::factory()->name('Local')->create(); + Activity::factory()->name('Remote')->inNami(123)->create(); $response = $this->get('/activity'); $this->assertInertiaHas('Local', $response, 'data.data.0.name'); $this->assertCount(1, $this->inertia($response, 'data.data')); } + + public function testItDisplaysDefaultFilter(): void + { + $this->login()->loginNami()->withoutExceptionHandling(); + + $response = $this->callFilter('activity.index', []); + + $this->assertInertiaHas(null, $response, 'data.meta.filter.subactivity'); + } + + public function testItFiltersActivityBySubactivity(): void + { + $this->login()->loginNami()->withoutExceptionHandling(); + $subactivity = Subactivity::factory()->name('jjon')->create(); + Activity::factory()->name('Local')->hasAttached($subactivity)->create(); + Activity::factory()->count(2)->name('Local')->create(); + + $response = $this->callFilter('activity.index', ['subactivity_id' => $subactivity->id]); + + $this->assertInertiaHas($subactivity->id, $response, 'data.meta.filter.subactivity_id'); + $this->assertCount(1, $this->inertia($response, 'data.data')); + } } diff --git a/tests/Lib/MakesHttpCalls.php b/tests/Lib/MakesHttpCalls.php new file mode 100644 index 00000000..83c35276 --- /dev/null +++ b/tests/Lib/MakesHttpCalls.php @@ -0,0 +1,28 @@ + $filter + */ + public function callFilter(string $routeName, array $filter): TestResponse + { + return $this->call('GET', $this->filterUrl($routeName, $filter)); + } + + /** + * @param array $filter + */ + public function filterUrl(string $routeName, array $filter): string + { + $params = [ + 'filter' => base64_encode(json_encode($filter)), + ]; + + return route($routeName, $params); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 55ef8a62..d401998d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -11,6 +11,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Http; use Illuminate\Testing\TestResponse; use Phake; +use Tests\Lib\MakesHttpCalls; use Tests\Lib\TestsInertia; use Zoomyboy\LaravelNami\Authentication\Auth; @@ -18,6 +19,7 @@ abstract class TestCase extends BaseTestCase { use CreatesApplication; use TestsInertia; + use MakesHttpCalls; protected User $me;