Add assign for participant
continuous-integration/drone/push Build is failing Details

This commit is contained in:
philipp lang 2024-07-02 18:04:55 +02:00
parent 0741984858
commit 445a8ae962
8 changed files with 133 additions and 10 deletions

View File

@ -0,0 +1,27 @@
<?php
namespace App\Form\Actions;
use App\Form\Models\Participant;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class ParticipantAssignAction
{
use AsAction;
/**
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'member_id' => 'required|exists:members,id',
];
}
public function handle(Participant $participant, ActionRequest $request): void
{
$participant->update(['member_id' => $request->input('member_id')]);
}
}

View File

@ -27,7 +27,9 @@ class ParticipantResource extends JsonResource
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
'created_at_display' => $this->created_at->format('d.m.Y'),
'children_count' => $this->children_count,
'member_id' => $this->member_id,
'links' => [
'assign' => route('participant.assign', ['participant' => $this->getModel()]),
'destroy' => route('participant.destroy', ['participant' => $this->getModel()]),
'children' => route('form.participant.index', ['form' => $this->form, 'parent' => $this->id])
]

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve"><path d="M501.362 383.95 320.497 51.474c-29.059-48.921-99.896-48.986-128.994 0L10.647 383.95c-29.706 49.989 6.259 113.291 64.482 113.291h361.736c58.174 0 94.203-63.251 64.497-113.291zM256 437.241c-16.538 0-30-13.462-30-30s13.462-30 30-30 30 13.462 30 30-13.462 30-30 30zm30-120c0 16.538-13.462 30-30 30s-30-13.462-30-30v-150c0-16.538 13.462-30 30-30s30 13.462 30 30v150z" fill="currentColor" /></svg>

After

Width:  |  Height:  |  Size: 526 B

View File

@ -0,0 +1,46 @@
<template>
<div>
<div class="mt-4">
<f-text id="search_string" v-model="searchString" label="Mitglied finden"></f-text>
</div>
<div v-if="results !== null" class="mt-5 sm:mt-10 space-y-2">
<a v-for="member in results.hits" :key="member.id" href="#" @click.prevent="emit('assign', member.id)">
<div class="flex items-center justify-between hover:bg-sky-600/20 transition text-sky-300 px-3 sm:px-6 py-1 sm:py-3 rounded-lg">
<div class="flex space-x-2 items-center">
<div class="w-5 sm:w-16 flex flex-none">
<ui-age-groups icon-class="w-4 h-4 sm:w-6 sm:h-6" class="flex-col sm:flex-row" :member="member"></ui-age-groups>
</div>
<div class="flex items-baseline flex-col md:flex-row">
<span class="text-lg" v-text="member.fullname"></span>
<span class="ml-2 text-xs" v-text="member.group_name"></span>
</div>
</div>
</div>
</a>
</div>
</div>
</template>
<script setup>
import {computed, ref} from 'vue';
import useSearch from '../../composables/useSearch.js';
const emit = defineEmits(['assign']);
const {search} = useSearch();
const realSearchString = ref('');
const results = ref(null);
const searchString = computed({
get: () => realSearchString.value,
set: async (v) => {
realSearchString.value = v;
if (!v.length) {
results.value = null;
return;
}
results.value = await search(v, [], {limit: 10});
},
});
</script>

View File

@ -1,5 +1,8 @@
<template>
<div class="mt-5">
<ui-popup v-if="assigning !== null" heading="Mitglied zuweisen" closeable @close="assigning = null">
<member-assign @assign="assign"></member-assign>
</ui-popup>
<ui-popup v-if="deleting !== null" heading="Teilnehmer*in löschen?" @close="deleting = null">
<div>
<p class="mt-4">Den*Die Teilnehmer*in löschen?</p>
@ -58,6 +61,10 @@
<template v-for="(participant, index) in data" :key="index">
<tr>
<td v-for="(column, columnindex) in activeColumns" :key="column.id">
<div class="flex items-center space-x-2">
<button v-if="columnindex === 0 && participant.member_id === null" v-tooltip="`kein Mitglied zugewiesen. Per Klick zuweisen`" @click.prevent="assigning = participant">
<ui-sprite src="warning-triangle" class="text-yellow-400 w-5 h-5"></ui-sprite>
</button>
<ui-table-toggle-button
v-if="columnindex === 0 && groupParticipants"
:value="participant"
@ -67,6 +74,7 @@
@toggle="toggle(participant)"
></ui-table-toggle-button>
<div v-else v-text="participant[column.display_attribute]"></div>
</div>
</td>
<td>
<a v-tooltip="`Bearbeiten`" href="#" class="ml-2 inline-flex btn btn-warning btn-sm" @click.prevent="edit(participant)"><ui-sprite src="pencil"></ui-sprite></a>
@ -104,10 +112,13 @@
import {watch, ref, computed} from 'vue';
import {useApiIndex} from '../../composables/useApiIndex.js';
import useTableToggle from '../../composables/useTableToggle.js';
import MemberAssign from './MemberAssign.vue';
const deleting = ref(null);
const {isOpen, toggle, childrenOf, clearToggle} = useTableToggle({});
const assigning = ref(null);
const props = defineProps({
url: {
type: String,
@ -137,6 +148,12 @@ const groupParticipants = computed({
},
});
async function assign(memberId) {
await axios.post(assigning.value.links.assign, {member_id: memberId});
reload();
assigning.value = null;
}
var {meta, data, reload, reloadPage, axios, remove, toFilterString, url, updateUrl} = useApiIndex(props.hasNamiField ? props.rootUrl : props.url, 'participant');
const activeColumns = computed(() => meta.value.columns.filter((c) => meta.value.form_meta.active_columns.includes(c.id)));

View File

@ -36,6 +36,7 @@ use App\Form\Actions\FormtemplateUpdateAction;
use App\Form\Actions\FormUpdateAction;
use App\Form\Actions\FormUpdateMetaAction;
use App\Form\Actions\IsDirtyAction;
use App\Form\Actions\ParticipantAssignAction;
use App\Form\Actions\ParticipantDestroyAction;
use App\Form\Actions\ParticipantIndexAction;
use App\Initialize\Actions\InitializeAction;
@ -170,6 +171,7 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::get('/form/{form}/participants/{parent?}', ParticipantIndexAction::class)->name('form.participant.index');
Route::post('/form/{form}/is-dirty', IsDirtyAction::class)->name('form.is-dirty');
Route::delete('/participant/{participant}', ParticipantDestroyAction::class)->name('participant.destroy');
Route::post('/participant/{participant}/assign', ParticipantAssignAction::class)->name('participant.assign');
// ------------------------------------ fileshare -----------------------------------
Route::post('/fileshare', FileshareStoreAction::class)->name('fileshare.store');

View File

@ -0,0 +1,26 @@
<?php
namespace Tests\Feature\Form;
use App\Form\Models\Form;
use App\Form\Models\Participant;
use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class ParticipantAssignActionTest extends TestCase
{
use DatabaseTransactions;
public function testItAssignsAParticipantToAMenber(): void
{
$this->login()->loginNami();
$participant = Participant::factory()->for(Form::factory())->create();
$member = Member::factory()->defaults()->create();
$this->postJson(route('participant.assign', ['participant' => $participant]), ['member_id' => $member->id])->assertOk();
$this->assertEquals($member->id, $participant->fresh()->member_id);
}
}

View File

@ -20,7 +20,7 @@ class ParticipantIndexActionTest extends FormTestCase
$this->login()->loginNami()->withoutExceptionHandling();
$group = Group::factory()->innerName('Stamm')->create();
$form = Form::factory()
->has(Participant::factory()->data(['vorname' => 'Max', 'select' => ['A', 'B'], 'stufe' => 'Pfadfinder', 'test1' => '', 'test2' => '', 'test3' => '', 'birthday' => '1991-04-20', 'bezirk' => $group->id]))
->has(Participant::factory()->state(['member_id' => 55])->data(['vorname' => 'Max', 'select' => ['A', 'B'], 'stufe' => 'Pfadfinder', 'test1' => '', 'test2' => '', 'test3' => '', 'birthday' => '1991-04-20', 'bezirk' => $group->id]))
->fields([
$this->textField('vorname')->name('Vorname'),
$this->checkboxesField('select')->options(['A', 'B', 'C']),
@ -40,12 +40,14 @@ class ParticipantIndexActionTest extends FormTestCase
->assertJsonPath('data.0.vorname_display', 'Max')
->assertJsonPath('data.0.stufe', 'Pfadfinder')
->assertJsonPath('data.0.bezirk', $group->id)
->assertJsonPath('data.0.member_id', 55)
->assertJsonPath('data.0.bezirk_display', 'Stamm')
->assertJsonPath('data.0.birthday_display', '20.04.1991')
->assertJsonPath('data.0.birthday', '1991-04-20')
->assertJsonPath('data.0.select', ['A', 'B'])
->assertJsonPath('data.0.select_display', 'A, B')
->assertJsonPath('data.0.links.destroy', route('participant.destroy', ['participant' => $form->participants->first()]))
->assertJsonPath('data.0.links.assign', route('participant.assign', ['participant' => $form->participants->first()]))
->assertJsonPath('meta.columns.0.name', 'Vorname')
->assertJsonPath('meta.columns.0.base_type', class_basename(TextField::class))
->assertJsonPath('meta.columns.0.id', 'vorname')