Add: Edit subactivity
continuous-integration/drone/push Build is failing Details

This commit is contained in:
philipp lang 2023-03-07 01:35:39 +01:00
parent 4cb2dddb43
commit 9598a44e13
13 changed files with 162 additions and 21 deletions

View File

@ -32,6 +32,7 @@ class ActivityStoreAction
'name' => 'required|max:255',
'is_filterable' => 'present|boolean',
'subactivities' => 'present|array',
'subactivities.*' => 'integer',
];
}

View File

@ -38,6 +38,7 @@ class ActivityUpdateAction
'name' => 'required|max:255',
'is_filterable' => 'present|boolean',
'subactivities' => 'present|array',
'subactivities.*' => 'integer',
];
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Activity\Api;
use App\Resources\SubactivityResource;
use App\Subactivity;
use Illuminate\Http\JsonResponse;
use Lorisleiva\Actions\Concerns\AsAction;
class SubactivityShowAction
{
use AsAction;
public function handle()
{
// ...
}
public function asController(Subactivity $subactivity): JsonResponse
{
return response()->json([
'data' => new SubactivityResource($subactivity),
'meta' => SubactivityResource::meta(),
]);
}
}

View File

@ -34,6 +34,7 @@ class SubactivityStoreAction
return [
'name' => 'required|unique:subactivities,name',
'activities' => 'present|array|min:1',
'activities.*' => 'integer',
'is_filterable' => 'present|boolean',
];
}

View File

@ -41,6 +41,7 @@ class SubactivityUpdateAction
return [
'name' => ['required', 'string', 'max:255', Rule::unique('subactivities', 'name')->ignore(request()->route('subactivity')->id)],
'activities' => ['present', 'array', 'min:1'],
'activities.*' => 'integer',
'is_filterable' => 'present|boolean',
];
}

View File

@ -5,6 +5,7 @@ namespace App\Activity\Resources;
use App\Activity;
use App\Http\Views\ActivityFilterScope;
use App\Lib\HasMeta;
use App\Resources\SubactivityResource;
use App\Subactivity;
use Illuminate\Http\Resources\Json\JsonResource;
@ -49,7 +50,7 @@ class ActivityResource extends JsonResource
public static function meta(): array
{
return [
'subactivities' => Subactivity::select('name', 'id', 'is_filterable')->get(),
'subactivities' => SubactivityResource::collectionWithoutMeta(Subactivity::get()),
'filter' => ActivityFilterScope::fromRequest(request()->input('filter')),
];
}

View File

@ -25,9 +25,13 @@ trait HasMeta
]);
}
public static function collectionWithoutMeta($resource)
{
return parent::collection($resource);
}
public static function meta(): array
{
return [];
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Resources;
use App\Activity;
use App\Activity\Resources\ActivityResource;
use App\Lib\HasMeta;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Subactivity
*/
class SubactivityResource extends JsonResource
{
use HasMeta;
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
*
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'is_filterable' => $this->is_filterable,
'activities' => $this->activities->pluck('id')->toArray(),
'links' => [
'show' => route('api.subactivity.show', ['subactivity' => $this->getModel()]),
'update' => route('api.subactivity.update', ['subactivity' => $this->getModel()]),
],
];
}
public static function meta(): array
{
return [
'activities' => ActivityResource::collectionWithoutMeta(Activity::get()),
];
}
}

View File

@ -1,6 +1,6 @@
<template>
<div>
<div class="flex space-x-3">
<div class="flex space-x-3" v-if="model">
<f-text size="sm" id="name" v-model="model.name" label="Name" required></f-text>
<f-switch size="sm" v-model="model.is_filterable" name="subactivity_is_filterable" id="subactivity_is_filterable" label="Filterbar"></f-switch>
</div>
@ -25,12 +25,26 @@ export default {
methods: {
async store() {
try {
var response = await this.axios.post('/subactivity', this.model);
this.$emit('stored', response.data);
if (this.model.id) {
var response = await this.axios.patch(this.model.links.update, this.model);
this.$emit('updated', response.data);
} else {
var response = await this.axios.post('/subactivity', this.model);
this.$emit('stored', response.data);
}
} catch (e) {
this.errorsFromException(e);
}
},
},
async created() {
if (this.value.id) {
var payload = (await this.axios.get(this.value.links.show)).data;
this.model = payload.data;
} else {
this.model = this.value;
}
},
};
</script>

View File

@ -1,7 +1,7 @@
<template>
<form id="actionform" class="grow p-3" @submit.prevent="submit">
<popup heading="Neue Untertätigkeit" v-if="mode === 'edit' && addingSubactivity === true" @close="addingSubactivity = false">
<subactivity-form class="mt-4" :value="inner.subactivity_model" @stored="reloadSubactivities"></subactivity-form>
<popup heading="Neue Untertätigkeit" v-if="mode === 'edit' && currentSubactivity !== null" @close="currentSubactivity = null">
<subactivity-form class="mt-4" v-if="currentSubactivity" :value="currentSubactivity" @stored="reloadSubactivities" @updated="mergeSubactivity"></subactivity-form>
</popup>
<div class="flex space-x-3">
<f-text id="name" v-model="inner.name" label="Name" required></f-text>
@ -9,20 +9,15 @@
</div>
<div class="flex space-x-3 items-center mt-6 mb-2">
<checkboxes-label>Untertätigkeiten</checkboxes-label>
<icon-button icon="plus" v-if="mode === 'edit'" @click.prevent="addingSubactivity = true">Neu</icon-button>
<icon-button icon="plus" v-if="mode === 'edit'" @click.prevent="currentSubactivity = inner.subactivity_model">Neu</icon-button>
</div>
<div class="grid gap-2 sm:grid-cols-2 md:grid-cols-4">
<f-switch
inline
size="sm"
:key="option.id"
v-model="inner.subactivities"
name="subactivities[]"
:id="`subactivities-${option.id}`"
:value="option.id"
:label="option.name"
v-for="option in subactivities"
></f-switch>
<div v-for="option in subactivities" class="flex items-center space-x-2">
<a href="#" @click.prevent="currentSubactivity = option" class="transition hover:bg-yellow-600 group w-5 h-5 rounded-full flex items-center justify-center flex-none">
<svg-sprite src="pencil" class="text-yellow-800 w-3 h-3 group-hover:text-yellow-200 transition"></svg-sprite>
</a>
<f-switch inline size="sm" :key="option.id" v-model="inner.subactivities" name="subactivities[]" :id="`subactivities-${option.id}`" :value="option.id" :label="option.name"></f-switch>
</div>
</div>
<save-button form="actionform"></save-button>
</form>
@ -32,7 +27,7 @@
export default {
data: function () {
return {
addingSubactivity: false,
currentSubactivity: null,
subactivities: [...this.meta.subactivities],
inner: {...this.data},
mode: this.data.name === '' ? 'create' : 'edit',
@ -63,7 +58,20 @@ export default {
_self.subactivities = page.props.meta.subactivities;
_self.inner.subactivities.push(model.id);
_self.$success('Untertätigkeit gespeichert.');
_self.addingSubactivity = false;
_self.currentSubactivity = null;
},
});
},
mergeSubactivity(model) {
var _self = this;
this.$inertia.reload({
onSuccess(page) {
_self.subactivities = page.props.meta.subactivities;
_self.inner.subactivities = _self.inner.subactivities.map((s) => (s.id === model.id ? model : s));
_self.$success('Untertätigkeit aktualisiert.');
_self.currentSubactivity = null;
},
});
},

View File

@ -6,6 +6,7 @@ use App\Activity\Actions\CreateAction as ActivityCreateAction;
use App\Activity\Actions\DestroyAction as ActivityDestroyAction;
use App\Activity\Actions\EditAction as ActivityEditAction;
use App\Activity\Actions\IndexAction as ActivityIndexAction;
use App\Activity\Api\SubactivityShowAction;
use App\Activity\Api\SubactivityStoreAction;
use App\Activity\Api\SubactivityUpdateAction;
use App\Contribution\Actions\FormAction as ContributionFormAction;
@ -64,4 +65,5 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::delete('/activity/{activity}', ActivityDestroyAction::class)->name('activity.destroy');
Route::post('/subactivity', SubactivityStoreAction::class)->name('api.subactivity.store');
Route::patch('/subactivity/{subactivity}', SubactivityUpdateAction::class)->name('api.subactivity.update');
Route::get('/subactivity/{subactivity}', SubactivityShowAction::class)->name('api.subactivity.show');
});

View File

@ -34,5 +34,14 @@ class EditTest extends TestCase
'name' => 'Pupu',
'is_filterable' => true,
], $response, 'meta.subactivities.0');
$this->assertInertiaHas([
'id' => $activity->subactivities->first()->id,
'name' => 'Pupu',
'is_filterable' => true,
'links' => [
'show' => route('api.subactivity.show', ['subactivity' => $activity->subactivities->first()->id]),
'update' => route('api.subactivity.update', ['subactivity' => $activity->subactivities->first()->id]),
],
], $response, 'meta.subactivities.0');
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Tests\Feature\Activity;
use App\Activity;
use App\Subactivity;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class SubactivityShowTest extends TestCase
{
use DatabaseTransactions;
public function testItShowsASubactivity(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$subactivity = Subactivity::factory()->name('Asas')->filterable()->hasAttached(Activity::factory())->create();
$response = $this->getJson(route('api.subactivity.show', ['subactivity' => $subactivity]));
$response->assertJsonPath('data.id', $subactivity->id);
$response->assertJsonPath('data.name', $subactivity->name);
$response->assertJsonPath('data.links.update', route('api.subactivity.update', ['subactivity' => $subactivity]));
$response->assertJsonPath('data.links.show', route('api.subactivity.show', ['subactivity' => $subactivity]));
$response->assertJsonPath('data.activities.0', $subactivity->activities->first()->id);
$response->assertJsonPath('meta.activities.0.name', $subactivity->activities->first()->name);
$response->assertJsonPath('meta.activities.0.id', $subactivity->activities->first()->id);
}
}