Compare commits

..

No commits in common. "1630bce7950be3d3579e20186f4a989610f6aa49" and "1a5675634f7cef734f1b4fa5e94551b302115a3e" have entirely different histories.

11 changed files with 56 additions and 161 deletions

View File

@ -37,7 +37,7 @@ class ParticipantIndexAction
$data = match ($parent) {
null => $this->handle($form, $filter),
-1 => $this->getQuery($form, $filter)->where('parent_id', null)->paginate(15),
-1 => $this->getQuery($form, $filter)->where('parent_id', null)->get(),
default => $this->getQuery($form, $filter)->where('parent_id', $parent)->get(),
};

View File

@ -35,11 +35,6 @@ class FieldCollection extends Collection
return $this->filter(fn ($field) => !is_a($field, NamiField::class));
}
public function hasNamiField(): bool
{
return $this->first(fn ($field) => is_a($field, NamiField::class)) !== null;
}
/**
* @return stdClass
*/

View File

@ -46,10 +46,8 @@ class FormResource extends JsonResource
'participants_count' => $this->participants_count,
'is_active' => $this->is_active,
'is_private' => $this->is_private,
'has_nami_field' => $this->getFields()->hasNamiField(),
'links' => [
'participant_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => null]),
'participant_root_index' => route('form.participant.index', ['form' => $this->getModel(), 'parent' => -1]),
'participant_index' => route('form.participant.index', ['form' => $this->getModel()]),
'update' => route('form.update', ['form' => $this->getModel()]),
'destroy' => route('form.destroy', ['form' => $this->getModel()]),
'is_dirty' => route('form.is-dirty', ['form' => $this->getModel()]),

View File

@ -23,13 +23,11 @@ class ParticipantResource extends JsonResource
{
return [
...$this->getModel()->getFields()->present(),
'id' => $this->id,
'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,
'links' => [
'destroy' => route('participant.destroy', ['participant' => $this->getModel()]),
'children' => route('form.participant.index', ['form' => $this->form, 'parent' => $this->id])
]
];
}
@ -59,7 +57,6 @@ class ParticipantResource extends JsonResource
'default_filter_value' => ParticipantFilterScope::$nan,
'filters' => $filterData,
'form_meta' => $form->meta,
'has_nami_field' => $form->getFields()->hasNamiField(),
'links' => [
'update_form_meta' => route('form.update-meta', ['form' => $form]),
],

View File

@ -2,12 +2,10 @@ import {ref, inject, onBeforeUnmount} from 'vue';
import {router} from '@inertiajs/vue3';
import useQueueEvents from './useQueueEvents.js';
export function useApiIndex(firstUrl, siteName) {
export function useApiIndex(url, siteName) {
const axios = inject('axios');
const {startListener, stopListener} = useQueueEvents(siteName, () => reload());
const single = ref(null);
const url = ref(firstUrl);
const inner = {
data: ref([]),
meta: ref({}),
@ -19,7 +17,7 @@ export function useApiIndex(firstUrl, siteName) {
...p,
};
var response = (await axios.get(url.value, {params})).data;
var response = (await axios.get(url, {params})).data;
inner.data.value = response.data;
inner.meta.value = response.meta;
}
@ -74,10 +72,6 @@ export function useApiIndex(firstUrl, siteName) {
single.value = null;
}
function updateUrl(newUrl) {
url.value = newUrl;
}
startListener();
onBeforeUnmount(() => stopListener());
@ -97,7 +91,5 @@ export function useApiIndex(firstUrl, siteName) {
cancel,
axios,
toFilterString,
updateUrl,
url,
};
}

View File

@ -1,34 +0,0 @@
import {computed, ref, inject} from 'vue';
export default function (init) {
const axios = inject('axios');
const children = ref(init);
function isOpen(child) {
return child in children.value;
}
async function toggle(parent) {
if (isOpen(parent.id)) {
delete children.value[parent.id];
} else {
children.value[parent.id] = (await axios.get(parent.links.children)).data.data;
}
}
function childrenOf(parentId) {
return children.value[parentId] ? children.value[parentId] : [];
}
function clearToggle() {
children.value = {};
}
return {
isOpen,
toggle,
childrenOf,
clearToggle,
};
}

View File

@ -32,7 +32,7 @@
</ui-popup>
<ui-popup v-if="showing !== null" :heading="`Teilnehmende für ${showing.name}`" full @close="showing = null">
<participants :has-nami-field="showing.has_nami_field" :root-url="showing.links.participant_root_index" :url="showing.links.participant_index"> </participants>
<participants :url="showing.links.participant_index"></participants>
</ui-popup>
<ui-popup v-if="single !== null && single.config !== null" :heading="`Veranstaltung ${single.id ? 'bearbeiten' : 'erstellen'}`" full @close="cancel">

View File

@ -10,7 +10,6 @@
</div>
</ui-popup>
<page-filter breakpoint="lg">
<f-switch v-if="meta.has_nami_field" id="group_participants" v-model="groupParticipants" label="Gruppieren" size="sm" name="group_participants"></f-switch>
<f-multipleselect id="active_columns" v-model="activeColumnsConfig" :options="meta.columns" label="Aktive Spalten" size="sm" name="active_columns"></f-multipleselect>
<template v-for="(filter, index) in meta.filters">
@ -55,44 +54,15 @@
<th></th>
</thead>
<template v-for="(participant, index) in data" :key="index">
<tr>
<td v-for="(column, columnindex) in activeColumns" :key="column.id">
<ui-table-toggle-button
v-if="columnindex === 0 && groupParticipants"
:value="participant"
:text="participant[column.display_attribute]"
:level="0"
:active="isOpen(participant.id)"
@toggle="toggle(participant)"
></ui-table-toggle-button>
<div v-else v-text="participant[column.display_attribute]"></div>
<tr v-for="(participant, index) in data" :key="index">
<td v-for="column in activeColumns" :key="column.id">
<div v-text="participant[column.display_attribute]"></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>
<a v-tooltip="`Löschen`" href="#" class="ml-2 inline-flex btn btn-danger btn-sm" @click.prevent="deleting = participant"><ui-sprite src="trash"></ui-sprite></a>
</td>
</tr>
<template v-for="child in childrenOf(participant.id)" :key="child.id">
<tr>
<td v-for="(column, columnindex) in activeColumns" :key="column.id">
<ui-table-toggle-button
v-if="columnindex === 0 && groupParticipants"
:value="child"
:text="child[column.display_attribute]"
:level="1"
:active="isOpen(child.id)"
@toggle="toggle(child)"
></ui-table-toggle-button>
<div v-else v-text="child[column.display_attribute]"></div>
</td>
<td>
<a v-tooltip="`Bearbeiten`" href="#" class="ml-2 inline-flex btn btn-warning btn-sm" @click.prevent="edit(child)"><ui-sprite src="pencil"></ui-sprite></a>
<a v-tooltip="`Löschen`" href="#" class="ml-2 inline-flex btn btn-danger btn-sm" @click.prevent="deleting = child"><ui-sprite src="trash"></ui-sprite></a>
</td>
</tr>
</template>
</template>
</table>
<div class="px-6">
<ui-pagination class="mt-4" :value="meta" @reload="reloadPage"></ui-pagination>
@ -103,10 +73,8 @@
<script setup>
import {watch, ref, computed} from 'vue';
import {useApiIndex} from '../../composables/useApiIndex.js';
import useTableToggle from '../../composables/useTableToggle.js';
const deleting = ref(null);
const {isOpen, toggle, childrenOf, clearToggle} = useTableToggle({});
const props = defineProps({
url: {
@ -114,30 +82,9 @@ const props = defineProps({
required: true,
validator: (value) => value.startsWith('http'),
},
rootUrl: {
type: String,
required: true,
validator: (value) => value.startsWith('http'),
},
hasNamiField: {
type: Boolean,
required: true,
},
});
const groupParticipants = computed({
get() {
return url.value === props.rootUrl;
},
async set(v) {
updateUrl(v ? props.rootUrl : props.url);
if (!v) {
clearToggle();
}
await reload();
},
});
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} = useApiIndex(props.url, 'participant');
const activeColumns = computed(() => meta.value.columns.filter((c) => meta.value.form_meta.active_columns.includes(c.id)));

View File

@ -89,16 +89,25 @@
</template>
<script setup>
import {ref} from 'vue';
import {ref, reactive} from 'vue';
import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js';
import useTableToggle from '../../composables/useTableToggle.js';
const props = defineProps(indexProps);
var {axios, meta, data} = useIndex(props.data, 'invoice');
const {isOpen, toggle, childrenOf} = useTableToggle({null: data.value});
const children = reactive({
null: data.value,
});
var editing = ref(null);
async function toggle(parent) {
if (isOpen(parent.id)) {
delete children[parent.id];
} else {
children[parent.id] = (await axios.get(parent.links.children)).data.data;
}
}
async function edit(parent) {
editing.value = {
parent: parent,
@ -106,6 +115,14 @@ async function edit(parent) {
};
}
function isOpen(child) {
return child in children;
}
function childrenOf(parent) {
return children[parent] ? children[parent] : [];
}
async function store() {
await axios.post(meta.value.links.bulkstore, [editing.value.parent, ...editing.value.children]);
children[editing.value.parent.id] = (await axios.get(editing.value.parent.links.children)).data.data;

View File

@ -83,18 +83,6 @@ class FormIndexActionTest extends FormTestCase
->assertInertiaCount('data.data', 2);
}
public function testItDisplaysParentLinkForFormWithNamiFields(): void
{
$this->withoutExceptionHandling()->login()->loginNami();
$form = Form::factory()->fields([$this->namiField('mitglieder')])->create();
sleep(1);
$this->callFilter('form.index', [])
->assertInertiaPath('data.data.0.has_nami_field', true)
->assertInertiaPath('data.data.0.links.participant_root_index', route('form.participant.index', ['form' => $form, 'parent' => -1]))
->assertInertiaPath('data.data.0.links.participant_index', route('form.participant.index', ['form' => $form, 'parent' => null]));
}
public function testItDisplaysRegisterUrl(): void
{
$this->withoutExceptionHandling()->login()->loginNami();

View File

@ -21,7 +21,8 @@ class ParticipantIndexActionTest extends FormTestCase
$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]))
->fields([
->sections([
FormtemplateSectionRequest::new()->fields([
$this->textField('vorname')->name('Vorname'),
$this->checkboxesField('select')->options(['A', 'B', 'C']),
$this->dropdownField('stufe')->options(['Wölfling', 'Jungpfadfinder', 'Pfadfinder']),
@ -30,12 +31,12 @@ class ParticipantIndexActionTest extends FormTestCase
$this->textField('test3')->name('Test 3'),
$this->dateField('birthday')->name('Geburtsdatum'),
$this->groupField('bezirk')->name('bezirk'),
]),
])
->create();
$this->callFilter('form.participant.index', [], ['form' => $form])
->assertOk()
->assertJsonPath('data.0.id', $form->participants->first()->id)
->assertJsonPath('data.0.vorname', 'Max')
->assertJsonPath('data.0.vorname_display', 'Max')
->assertJsonPath('data.0.stufe', 'Pfadfinder')
@ -52,7 +53,6 @@ class ParticipantIndexActionTest extends FormTestCase
->assertJsonPath('meta.columns.6.display_attribute', 'birthday_display')
->assertJsonPath('meta.columns.0.display_attribute', 'vorname_display')
->assertJsonPath('meta.form_meta.active_columns', ['vorname', 'select', 'stufe', 'test1'])
->assertJsonPath('meta.has_nami_field', false)
->assertJsonPath('meta.links.update_form_meta', route('form.update-meta', ['form' => $form]))
->assertJsonPath('meta.form_meta.sorting', ['vorname', 'asc']);
}
@ -74,13 +74,6 @@ class ParticipantIndexActionTest extends FormTestCase
$this->callFilter('form.participant.index', ['data' => []], ['form' => $form])->assertJsonPath('meta.filter.data.check', ParticipantFilterScope::$nan);
}
public function testItDisplaysHasNamiField(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()->fields([$this->namiField('mitglieder')])->create();
$this->callFilter('form.participant.index', [], ['form' => $form])->assertJsonPath('meta.has_nami_field', true);
}
public function testItFiltersParticipantsByCheckboxValue(): void
{
$this->login()->loginNami()->withoutExceptionHandling();
@ -140,8 +133,10 @@ class ParticipantIndexActionTest extends FormTestCase
->has(Participant::factory()->data(['mitglieder' => [['id' => 393], ['id' => 394]]]))
->has(Participant::factory()->nr(393)->data(['mitglieder' => []]))
->has(Participant::factory()->nr(394)->data(['mitglieder' => []]))
->fields([
->sections([
FormtemplateSectionRequest::new()->fields([
$this->namiField('mitglieder'),
]),
])
->create();
@ -155,8 +150,10 @@ class ParticipantIndexActionTest extends FormTestCase
$this->login()->loginNami()->withoutExceptionHandling();
$form = Form::factory()
->has(Participant::factory()->data(['vorname' => 'Max']))
->fields([
->sections([
FormtemplateSectionRequest::new()->fields([
$this->textField('vorname')->name('Vorname'),
]),
])
->create();
@ -196,9 +193,7 @@ class ParticipantIndexActionTest extends FormTestCase
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => -1])
->assertJsonPath('data.0.children_count', 2)
->assertJsonPath('data.1.children_count', 0)
->assertJsonPath('data.0.links.children', route('form.participant.index', ['form' => $form, 'parent' => $participant->id]))
->assertJsonPath('meta.current_page', 1);
->assertJsonPath('data.1.children_count', 0);
$this->callFilter('form.participant.index', [], ['form' => $form, 'parent' => $participant->id])->assertJsonPath('data.0.children_count', 0);
}
}