Add assign for participant
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
0741984858
commit
445a8ae962
|
@ -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')]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,9 @@ class ParticipantResource extends JsonResource
|
||||||
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
|
||||||
'created_at_display' => $this->created_at->format('d.m.Y'),
|
'created_at_display' => $this->created_at->format('d.m.Y'),
|
||||||
'children_count' => $this->children_count,
|
'children_count' => $this->children_count,
|
||||||
|
'member_id' => $this->member_id,
|
||||||
'links' => [
|
'links' => [
|
||||||
|
'assign' => route('participant.assign', ['participant' => $this->getModel()]),
|
||||||
'destroy' => route('participant.destroy', ['participant' => $this->getModel()]),
|
'destroy' => route('participant.destroy', ['participant' => $this->getModel()]),
|
||||||
'children' => route('form.participant.index', ['form' => $this->form, 'parent' => $this->id])
|
'children' => route('form.participant.index', ['form' => $this->form, 'parent' => $this->id])
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 |
|
@ -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>
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-5">
|
<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">
|
<ui-popup v-if="deleting !== null" heading="Teilnehmer*in löschen?" @close="deleting = null">
|
||||||
<div>
|
<div>
|
||||||
<p class="mt-4">Den*Die Teilnehmer*in löschen?</p>
|
<p class="mt-4">Den*Die Teilnehmer*in löschen?</p>
|
||||||
|
@ -58,15 +61,20 @@
|
||||||
<template v-for="(participant, index) in data" :key="index">
|
<template v-for="(participant, index) in data" :key="index">
|
||||||
<tr>
|
<tr>
|
||||||
<td v-for="(column, columnindex) in activeColumns" :key="column.id">
|
<td v-for="(column, columnindex) in activeColumns" :key="column.id">
|
||||||
<ui-table-toggle-button
|
<div class="flex items-center space-x-2">
|
||||||
v-if="columnindex === 0 && groupParticipants"
|
<button v-if="columnindex === 0 && participant.member_id === null" v-tooltip="`kein Mitglied zugewiesen. Per Klick zuweisen`" @click.prevent="assigning = participant">
|
||||||
:value="participant"
|
<ui-sprite src="warning-triangle" class="text-yellow-400 w-5 h-5"></ui-sprite>
|
||||||
:text="participant[column.display_attribute]"
|
</button>
|
||||||
:level="0"
|
<ui-table-toggle-button
|
||||||
:active="isOpen(participant.id)"
|
v-if="columnindex === 0 && groupParticipants"
|
||||||
@toggle="toggle(participant)"
|
:value="participant"
|
||||||
></ui-table-toggle-button>
|
:text="participant[column.display_attribute]"
|
||||||
<div v-else v-text="participant[column.display_attribute]"></div>
|
:level="0"
|
||||||
|
:active="isOpen(participant.id)"
|
||||||
|
@toggle="toggle(participant)"
|
||||||
|
></ui-table-toggle-button>
|
||||||
|
<div v-else v-text="participant[column.display_attribute]"></div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<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>
|
<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 {watch, ref, computed} from 'vue';
|
||||||
import {useApiIndex} from '../../composables/useApiIndex.js';
|
import {useApiIndex} from '../../composables/useApiIndex.js';
|
||||||
import useTableToggle from '../../composables/useTableToggle.js';
|
import useTableToggle from '../../composables/useTableToggle.js';
|
||||||
|
import MemberAssign from './MemberAssign.vue';
|
||||||
|
|
||||||
const deleting = ref(null);
|
const deleting = ref(null);
|
||||||
const {isOpen, toggle, childrenOf, clearToggle} = useTableToggle({});
|
const {isOpen, toggle, childrenOf, clearToggle} = useTableToggle({});
|
||||||
|
|
||||||
|
const assigning = ref(null);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
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');
|
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)));
|
const activeColumns = computed(() => meta.value.columns.filter((c) => meta.value.form_meta.active_columns.includes(c.id)));
|
||||||
|
|
|
@ -36,6 +36,7 @@ use App\Form\Actions\FormtemplateUpdateAction;
|
||||||
use App\Form\Actions\FormUpdateAction;
|
use App\Form\Actions\FormUpdateAction;
|
||||||
use App\Form\Actions\FormUpdateMetaAction;
|
use App\Form\Actions\FormUpdateMetaAction;
|
||||||
use App\Form\Actions\IsDirtyAction;
|
use App\Form\Actions\IsDirtyAction;
|
||||||
|
use App\Form\Actions\ParticipantAssignAction;
|
||||||
use App\Form\Actions\ParticipantDestroyAction;
|
use App\Form\Actions\ParticipantDestroyAction;
|
||||||
use App\Form\Actions\ParticipantIndexAction;
|
use App\Form\Actions\ParticipantIndexAction;
|
||||||
use App\Initialize\Actions\InitializeAction;
|
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::get('/form/{form}/participants/{parent?}', ParticipantIndexAction::class)->name('form.participant.index');
|
||||||
Route::post('/form/{form}/is-dirty', IsDirtyAction::class)->name('form.is-dirty');
|
Route::post('/form/{form}/is-dirty', IsDirtyAction::class)->name('form.is-dirty');
|
||||||
Route::delete('/participant/{participant}', ParticipantDestroyAction::class)->name('participant.destroy');
|
Route::delete('/participant/{participant}', ParticipantDestroyAction::class)->name('participant.destroy');
|
||||||
|
Route::post('/participant/{participant}/assign', ParticipantAssignAction::class)->name('participant.assign');
|
||||||
|
|
||||||
// ------------------------------------ fileshare -----------------------------------
|
// ------------------------------------ fileshare -----------------------------------
|
||||||
Route::post('/fileshare', FileshareStoreAction::class)->name('fileshare.store');
|
Route::post('/fileshare', FileshareStoreAction::class)->name('fileshare.store');
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ class ParticipantIndexActionTest extends FormTestCase
|
||||||
$this->login()->loginNami()->withoutExceptionHandling();
|
$this->login()->loginNami()->withoutExceptionHandling();
|
||||||
$group = Group::factory()->innerName('Stamm')->create();
|
$group = Group::factory()->innerName('Stamm')->create();
|
||||||
$form = Form::factory()
|
$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([
|
->fields([
|
||||||
$this->textField('vorname')->name('Vorname'),
|
$this->textField('vorname')->name('Vorname'),
|
||||||
$this->checkboxesField('select')->options(['A', 'B', 'C']),
|
$this->checkboxesField('select')->options(['A', 'B', 'C']),
|
||||||
|
@ -40,12 +40,14 @@ class ParticipantIndexActionTest extends FormTestCase
|
||||||
->assertJsonPath('data.0.vorname_display', 'Max')
|
->assertJsonPath('data.0.vorname_display', 'Max')
|
||||||
->assertJsonPath('data.0.stufe', 'Pfadfinder')
|
->assertJsonPath('data.0.stufe', 'Pfadfinder')
|
||||||
->assertJsonPath('data.0.bezirk', $group->id)
|
->assertJsonPath('data.0.bezirk', $group->id)
|
||||||
|
->assertJsonPath('data.0.member_id', 55)
|
||||||
->assertJsonPath('data.0.bezirk_display', 'Stamm')
|
->assertJsonPath('data.0.bezirk_display', 'Stamm')
|
||||||
->assertJsonPath('data.0.birthday_display', '20.04.1991')
|
->assertJsonPath('data.0.birthday_display', '20.04.1991')
|
||||||
->assertJsonPath('data.0.birthday', '1991-04-20')
|
->assertJsonPath('data.0.birthday', '1991-04-20')
|
||||||
->assertJsonPath('data.0.select', ['A', 'B'])
|
->assertJsonPath('data.0.select', ['A', 'B'])
|
||||||
->assertJsonPath('data.0.select_display', '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.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.name', 'Vorname')
|
||||||
->assertJsonPath('meta.columns.0.base_type', class_basename(TextField::class))
|
->assertJsonPath('meta.columns.0.base_type', class_basename(TextField::class))
|
||||||
->assertJsonPath('meta.columns.0.id', 'vorname')
|
->assertJsonPath('meta.columns.0.id', 'vorname')
|
||||||
|
|
Loading…
Reference in New Issue