Add activity edit
This commit is contained in:
parent
067cbe6d9d
commit
197690069a
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Activity\Actions;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class DestroyAction
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Activity\Actions;
|
||||
|
||||
use App\Activity;
|
||||
use App\Activity\Resources\ActivityResource;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class EditAction
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Activity $activity): Response
|
||||
{
|
||||
return Inertia::render('activity/VForm', [
|
||||
'meta' => ActivityResource::meta(),
|
||||
'data' => new ActivityResource($activity->load('subactivities')),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ class IndexAction
|
|||
|
||||
public function asController(ActionRequest $request): Response
|
||||
{
|
||||
session()->put('menu', 'activity');
|
||||
session()->put('title', 'Tätigkeiten');
|
||||
$filter = ActivityFilterScope::fromRequest($request->input('filter'));
|
||||
|
||||
return Inertia::render('activity/VIndex', [
|
||||
|
|
|
@ -27,6 +27,12 @@ class ActivityResource extends JsonResource
|
|||
return [
|
||||
'name' => $this->name,
|
||||
'id' => $this->id,
|
||||
'subactivities' => $this->subactivities->pluck('id')->toArray(),
|
||||
'links' => [
|
||||
'edit' => route('activity.edit', ['activity' => $this->getModel()]),
|
||||
'update' => route('activity.update', ['activity' => $this->getModel()]),
|
||||
'destroy' => route('activity.destroy', ['activity' => $this->getModel()]),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -36,7 +42,7 @@ class ActivityResource extends JsonResource
|
|||
public static function meta(): array
|
||||
{
|
||||
return [
|
||||
'subactivities' => Subactivity::pluck('name', 'id'),
|
||||
'subactivities' => Subactivity::select('name', 'id')->get(),
|
||||
'filter' => ActivityFilterScope::fromRequest(request()->input('filter')),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<label class="flex flex-col relative field-switch cursor-pointer" :for="id" :class="sizes[size].wrap">
|
||||
<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"
|
||||
|
@ -69,9 +73,9 @@ export default {
|
|||
event: 'input',
|
||||
},
|
||||
props: {
|
||||
inset: {
|
||||
type: Boolean,
|
||||
inline: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
size: {
|
||||
default: 'base',
|
||||
|
@ -132,16 +136,7 @@ export default {
|
|||
var sizes = ['xxs', 'xs', 'sm', 'md', 'lg'];
|
||||
|
||||
var sizeIndex = sizes.findIndex((s) => s === this.size);
|
||||
return sizes[this.inset ? sizeIndex : sizeIndex - 1];
|
||||
},
|
||||
outerSize() {
|
||||
var sizes = ['xxs', 'xs', 'sm', 'md', 'lg'];
|
||||
|
||||
var sizeIndex = sizes.findIndex((s) => s === this.size);
|
||||
if (!this.label || this.inset) {
|
||||
sizeIndex--;
|
||||
}
|
||||
return sizes[sizeIndex];
|
||||
return sizes[sizeIndex - 1];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<div class="font-semibold text-gray-400 leading-none mb-2">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
inner: {...this.data},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
data: {},
|
||||
},
|
||||
methods: {
|
||||
reload(resetPage = true) {
|
||||
var _self = this;
|
||||
var data = {
|
||||
filter: btoa(JSON.stringify(this.inner.meta.filter)),
|
||||
page: 1,
|
||||
};
|
||||
|
||||
data['page'] = resetPage ? 1 : this.inner.meta.current_page;
|
||||
|
||||
this.$inertia.visit(window.location.pathname, {
|
||||
data,
|
||||
preserveState: true,
|
||||
onSuccess(page) {
|
||||
_self.inner = page.props.data;
|
||||
},
|
||||
});
|
||||
},
|
||||
can(permission) {
|
||||
return this.inner.meta.can[permission];
|
||||
},
|
||||
getFilter(value) {
|
||||
return this.inner.meta.filter[value];
|
||||
},
|
||||
setFilter(key, value) {
|
||||
this.inner.meta.filter[key] = value;
|
||||
this.reload();
|
||||
},
|
||||
requestCallback(successMessage, failureMessage) {
|
||||
return {
|
||||
onSuccess: () => {
|
||||
this.$success(successMessage);
|
||||
this.reload(false);
|
||||
},
|
||||
onFailure: () => {
|
||||
this.$error(failureMessage);
|
||||
this.reload(false);
|
||||
},
|
||||
preserveState: true,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<form id="actionform" class="grow p-3" @submit.prevent="submit">
|
||||
<f-text id="name" v-model="inner.name" label="Name" required></f-text>
|
||||
<checkboxes-label class="mt-4">Untertätigkeiten</checkboxes-label>
|
||||
<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 meta.subactivities"></f-switch>
|
||||
</div>
|
||||
<save-button form="actionform"></save-button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
inner: {...this.data},
|
||||
};
|
||||
},
|
||||
|
||||
props: {
|
||||
data: {},
|
||||
meta: {},
|
||||
},
|
||||
|
||||
components: {
|
||||
'checkboxes-label': () => import('../../components/Form/CheckboxesLabel'),
|
||||
},
|
||||
|
||||
methods: {
|
||||
submit() {
|
||||
this.$inertia.patch(`/activity/${this.inner.id}`, this.inner);
|
||||
},
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
|
@ -1,157 +1,42 @@
|
|||
<template>
|
||||
<div class="pb-6">
|
||||
<member-filter
|
||||
:value="query.filter"
|
||||
:activities="filterActivities"
|
||||
:subactivities="filterSubactivities"
|
||||
:bill-kinds="billKinds"
|
||||
></member-filter>
|
||||
|
||||
<table cellspacing="0" cellpadding="0" border="0" class="custom-table custom-table-sm hidden md:table">
|
||||
<thead>
|
||||
<th></th>
|
||||
<th>Nachname</th>
|
||||
<th>Vorname</th>
|
||||
<th class="hidden 2xl:table-cell">Ort</th>
|
||||
<th>Tags</th>
|
||||
<th class="hidden xl:table-cell">Alter</th>
|
||||
<th class="hidden xl:table-cell" v-show="hasModule('bill')">Rechnung</th>
|
||||
<th v-show="hasModule('bill')">Ausstand</th>
|
||||
<th>Name</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
|
||||
<tr v-for="(member, index) in data.data" :key="index">
|
||||
<td><age-groups :member="member"></age-groups></td>
|
||||
<td v-text="member.lastname"></td>
|
||||
<td v-text="member.firstname"></td>
|
||||
<td class="hidden 2xl:table-cell" v-text="member.full_address"></td>
|
||||
<td><tags :member="member"></tags></td>
|
||||
<td class="hidden xl:table-cell" v-text="member.age"></td>
|
||||
<td class="hidden xl:table-cell" v-show="hasModule('bill')">
|
||||
<v-label :value="member.bill_kind_name" fallback="kein"></v-label>
|
||||
</td>
|
||||
<td v-show="hasModule('bill')">
|
||||
<v-label :value="member.pending_payment" fallback="---"></v-label>
|
||||
</td>
|
||||
<tr v-for="(activity, index) in inner.data" :key="index">
|
||||
<td v-text="activity.name"></td>
|
||||
<td>
|
||||
<actions :member="member" @sidebar="openSidebar(index, $event)" @remove="remove(member)"></actions>
|
||||
<div class="flex space-x-1">
|
||||
<i-link :href="activity.links.edit" class="inline-flex btn btn-warning btn-sm" v-tooltip="`bearbeiten`"><svg-sprite src="pencil"></svg-sprite></i-link>
|
||||
<i-link
|
||||
href="#"
|
||||
@click.prevent="$emit('remove')"
|
||||
class="inline-flex btn btn-danger btn-sm"
|
||||
v-tooltip="`Entfernen`"
|
||||
><svg-sprite src="trash"></svg-sprite
|
||||
></i-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="md:hidden p-3 grid gap-3">
|
||||
<box class="relative" :heading="member.fullname" v-for="(member, index) in data.data" :key="index">
|
||||
<div slot="in-title">
|
||||
<age-groups class="ml-2" :member="member" icon-class="w-4 h-4"></age-groups>
|
||||
</div>
|
||||
<div class="text-xs text-gray-200" v-text="member.full_address"></div>
|
||||
<div class="flex items-center mt-1 space-x-4">
|
||||
<tags :member="member"></tags>
|
||||
<v-label
|
||||
class="text-gray-100 block"
|
||||
v-show="hasModule('bill')"
|
||||
:value="member.pending_payment"
|
||||
fallback=""
|
||||
></v-label>
|
||||
</div>
|
||||
<actions
|
||||
class="mt-2"
|
||||
:member="member"
|
||||
@sidebar="openSidebar(index, $event)"
|
||||
@remove="remove(member)"
|
||||
></actions>
|
||||
<div class="absolute right-0 top-0 h-full flex items-center mr-2">
|
||||
<i-link :href="member.links.show" v-tooltip="`Details`"
|
||||
><svg-sprite src="chevron-down" class="w-6 h-6 text-teal-100 -rotate-90"></svg-sprite
|
||||
></i-link>
|
||||
</div>
|
||||
</box>
|
||||
</div>
|
||||
|
||||
<div class="px-6">
|
||||
<v-pages class="mt-4" :value="data.meta" :only="['data']"></v-pages>
|
||||
</div>
|
||||
|
||||
<transition name="sidebar">
|
||||
<member-payments
|
||||
v-if="single !== null && sidebar === 'payment.index'"
|
||||
@close="closeSidebar"
|
||||
:subscriptions="subscriptions"
|
||||
:statuses="statuses"
|
||||
:value="data.data[single]"
|
||||
></member-payments>
|
||||
<member-memberships
|
||||
v-if="single !== null && sidebar === 'membership.index'"
|
||||
@close="closeSidebar"
|
||||
:activities="activities"
|
||||
:subactivities="subactivities"
|
||||
:value="data.data[single]"
|
||||
></member-memberships>
|
||||
<member-courses
|
||||
v-if="single !== null && sidebar === 'courses.index'"
|
||||
@close="closeSidebar"
|
||||
:courses="courses"
|
||||
:value="data.data[single]"
|
||||
></member-courses>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MemberPayments from './MemberPayments.vue';
|
||||
import MemberMemberships from './MemberMemberships.vue';
|
||||
import MemberCourses from './MemberCourses.vue';
|
||||
import MemberFilter from './MemberFilter.vue';
|
||||
import mergesQueryString from '../../mixins/mergesQueryString.js';
|
||||
import indexHelpers from '../../mixins/indexHelpers';
|
||||
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
sidebar: null,
|
||||
single: null,
|
||||
};
|
||||
},
|
||||
|
||||
mixins: [mergesQueryString],
|
||||
mixins: [indexHelpers],
|
||||
|
||||
components: {
|
||||
MemberMemberships,
|
||||
MemberPayments,
|
||||
MemberFilter,
|
||||
MemberCourses,
|
||||
'age-groups': () => import(/* webpackChunkName: "member" */ './AgeGroups'),
|
||||
'tags': () => import(/* webpackChunkName: "member" */ './Tags'),
|
||||
'actions': () => import(/* webpackChunkName: "member" */ './index/Actions'),
|
||||
},
|
||||
|
||||
methods: {
|
||||
remove(member) {
|
||||
if (window.confirm('Mitglied löschen?')) {
|
||||
this.$inertia.delete(`/member/${member.id}`);
|
||||
}
|
||||
},
|
||||
openSidebar(index, name) {
|
||||
this.single = index;
|
||||
this.sidebar = name;
|
||||
},
|
||||
closeSidebar() {
|
||||
this.single = null;
|
||||
this.sidebar = null;
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
data: {},
|
||||
subscriptions: {},
|
||||
statuses: {},
|
||||
paymentDefaults: {},
|
||||
query: {},
|
||||
billKinds: {},
|
||||
activities: {},
|
||||
subactivities: {},
|
||||
filterActivities: {},
|
||||
filterSubactivities: {},
|
||||
courses: {},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
use App\Activity\Actions\ActivityStoreAction;
|
||||
use App\Activity\Actions\ActivityUpdateAction;
|
||||
use App\Activity\Actions\IndexAction as ActivityIndexAction;
|
||||
use App\Activity\Actions\EditAction as ActivityEditAction;
|
||||
use App\Activity\Actions\DestroyAction as ActivityDestroyAction;
|
||||
use App\Contribution\Actions\FormAction as ContributionFormAction;
|
||||
use App\Contribution\ContributionController;
|
||||
use App\Course\Controllers\CourseController;
|
||||
|
@ -50,6 +52,8 @@ Route::group(['middleware' => 'auth:web'], function (): void {
|
|||
Route::get('/contribution', ContributionFormAction::class)->name('contribution.form');
|
||||
Route::get('/contribution/generate', [ContributionController::class, 'generate'])->name('contribution.generate');
|
||||
Route::get('/activity', ActivityIndexAction::class)->name('activity.index');
|
||||
Route::get('/activity/{activity}/edit', ActivityEditAction::class)->name('activity.edit');
|
||||
Route::post('/activity', ActivityStoreAction::class)->name('activity.store');
|
||||
Route::patch('/activity/{activity}', ActivityUpdateAction::class)->name('activity.update');
|
||||
Route::delete('/activity/{activity}', ActivityDestroyAction::class)->name('activity.destroy');
|
||||
});
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Activity;
|
||||
|
||||
use App\Activity;
|
||||
use App\Subactivity;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EditTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function testItEditsAnActivity(): void
|
||||
{
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
$activity = Activity::factory()->name('Asas')->hasAttached(Subactivity::factory()->name('Pupu'))->create();
|
||||
|
||||
$response = $this->get(route('activity.edit', ['activity' => $activity]));
|
||||
|
||||
$this->assertInertiaHas([
|
||||
'name' => 'Asas',
|
||||
'subactivities' => [$activity->subactivities->first()->id],
|
||||
], $response, 'data');
|
||||
$this->assertInertiaHas([
|
||||
'id' => $activity->subactivities->first()->id,
|
||||
'name' => 'Pupu',
|
||||
], $response, 'meta.subactivities.0');
|
||||
}
|
||||
|
||||
}
|
|
@ -14,12 +14,14 @@ class IndexTest extends TestCase
|
|||
public function testItDisplaysLocalActivities(): void
|
||||
{
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
Activity::factory()->name('Local')->create();
|
||||
$first = 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->assertInertiaHas(route('activity.update', ['activity' => $first]), $response, 'data.data.0.links.update');
|
||||
$this->assertInertiaHas(route('activity.destroy', ['activity' => $first]), $response, 'data.data.0.links.destroy');
|
||||
$this->assertCount(1, $this->inertia($response, 'data.data'));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue