Add group field

This commit is contained in:
philipp lang 2023-12-31 14:29:50 +01:00
parent c8bc69ae30
commit 89b5cbd4b5
13 changed files with 143 additions and 8 deletions

View File

@ -0,0 +1,39 @@
<?php
namespace App\Form\Fields;
use App\Group;
use App\Group\Enums\Level;
use Faker\Generator;
use Illuminate\Validation\Rule;
class GroupField extends Field
{
public static function name(): string
{
return 'Gruppierungs-Auswahl';
}
public static function meta(): array
{
return [
['key' => 'required', 'default' => false, 'rules' => ['required' => 'present|boolean'], 'label' => 'Erforderlich'],
['key' => 'parent_field', 'default' => null, 'rules' => ['parent_field' => 'present|nullable|string'], 'label' => 'Übergeordnetes Feld'],
['key' => 'parent_group', 'default' => null, 'rules' => ['parent_group' => ['present', 'nullable', Rule::in(Group::pluck('id')->toArray())]], 'label' => 'Übergeordnete Gruppierung'],
];
}
public static function default(): string
{
return '';
}
public static function fake(Generator $faker): array
{
return [
'required' => $faker->boolean(),
'parent_field' => null,
'parent_group' => null,
];
}
}

View File

@ -4,6 +4,8 @@ namespace App\Form\Resources;
use App\Form\Fields\Field;
use App\Form\Models\Formtemplate;
use App\Group;
use App\Group\Enums\Level;
use App\Lib\HasMeta;
use Illuminate\Http\Resources\Json\JsonResource;
@ -37,6 +39,8 @@ class FormtemplateResource extends JsonResource
public static function meta(): array
{
return [
'base_url' => url(''),
'groups' => Group::forSelect(),
'fields' => Field::asMeta(),
'links' => [
'store' => route('formtemplate.store'),

View File

@ -45,4 +45,17 @@ class Group extends Model
}
});
}
public static function forSelect(?self $parent = null, string $prefix = ''): array
{
$result = self::where('parent_id', $parent ? $parent->id : null)->withCount('children')->get();
return $result
->reduce(
fn ($before, $group) => $before->concat([['id' => $group->id, 'name' => $prefix . ($group->inner_name ?: $group->name)]])
->concat($group->children_count > 0 ? self::forSelect($group, $prefix . '-- ') : []),
collect([])
)
->toArray();
}
}

View File

@ -21,6 +21,8 @@
<script setup>
const props = defineProps({
modelValue: {},
meta: {},
payload: {},
});
const emit = defineEmits(['update:modelValue']);

View File

@ -17,6 +17,8 @@
<script setup>
const props = defineProps({
modelValue: {},
meta: {},
payload: {},
});
const emit = defineEmits(['update:modelValue']);

View File

@ -22,6 +22,8 @@
<script setup>
const props = defineProps({
modelValue: {},
meta: {},
payload: {},
});
const emit = defineEmits(['update:modelValue']);

View File

@ -21,13 +21,14 @@
>
<f-text id="fieldname" v-model="singleField.model.name" label="Name" size="sm" name="fieldname"></f-text>
<column-selector v-model="singleField.model.columns"></column-selector>
<component :is="fields[singleField.model.type]" v-model="singleField.model"></component>
<component :is="fields[singleField.model.type]" v-model="singleField.model" :payload="inner.sections" :meta="props.meta"></component>
</asideform>
</div>
<ui-box heading="Vorschau" container-class="grid gap-3" class="w-[800px]">
<event-form
editable
style="--primary: hsl(181, 75%, 26%); --secondary: hsl(181, 75%, 35%); --font: hsl(181, 84%, 78%); --circle: hsl(181, 86%, 16%)"
:base-url="meta.base_url"
:value="previewString"
@editSection="editSection($event.detail[0])"
@addSection="addSection"
@ -50,6 +51,7 @@ import TextField from './TextField.vue';
import DateField from './DateField.vue';
import DropdownField from './RadioField.vue';
import RadioField from './RadioField.vue';
import GroupField from './GroupField.vue';
import CheckboxField from './CheckboxField.vue';
import CheckboxesField from './CheckboxesField.vue';
import ColumnSelector from './ColumnSelector.vue';
@ -79,6 +81,7 @@ const fields = {
RadioField: RadioField,
CheckboxField: CheckboxField,
CheckboxesField: CheckboxesField,
GroupField: GroupField,
};
function editSection(sectionIndex) {

View File

@ -0,0 +1,49 @@
<template>
<f-switch
id="fieldrequired"
v-model="modelValue.required"
label="Erforderlich"
size="sm"
name="fieldrequired"
inline
@update:modelValue="$emit('update:modelValue', {...modelValue, required: $event})"
></f-switch>
<f-select
id="parent_field"
:options="fieldOptions"
size="sm"
name="parent_field"
label="Übergeordnetes Feld"
:model-value="modelValue.parent_field"
@update:modelValue="$emit('update:modelValue', {...props.modelValue, parent_field: $event})"
></f-select>
<f-select
id="parent_group"
:options="meta.groups"
size="sm"
name="parent_group"
label="Übergeordnete Gruppe"
:model-value="modelValue.parent_group"
@update:modelValue="$emit('update:modelValue', {...props.modelValue, parent_group: $event})"
></f-select>
</template>
<script setup>
import {computed} from 'vue';
const props = defineProps({
modelValue: {},
meta: {},
payload: {},
});
const fieldOptions = computed(() => {
return props.payload.reduce((carry, section) => {
return section.fields.reduce((fcarry, field) => {
return field.type === 'GroupField' ? fcarry.concat({id: field.key, name: field.name}) : fcarry;
}, carry);
}, []);
});
const emit = defineEmits(['update:modelValue']);
</script>

View File

@ -26,6 +26,8 @@
<script setup>
const props = defineProps({
modelValue: {},
meta: {},
payload: {},
});
const emit = defineEmits(['update:modelValue']);

View File

@ -1,11 +1,20 @@
<template>
<f-switch id="fieldrequired" :model-value="modelValue.required" label="Erforderlich" size="sm" name="fieldrequired"
inline @update:modelValue="$emit('update:modelValue', { ...modelValue, required: $event })"></f-switch>
<f-switch
id="fieldrequired"
:model-value="modelValue.required"
label="Erforderlich"
size="sm"
name="fieldrequired"
inline
@update:modelValue="$emit('update:modelValue', {...modelValue, required: $event})"
></f-switch>
</template>
<script setup>
const props = defineProps({
modelValue: {},
meta: {},
payload: {},
});
const emit = defineEmits(['update:modelValue']);

View File

@ -14,6 +14,8 @@
<script setup>
const props = defineProps({
modelValue: {},
meta: {},
payload: {},
});
const emit = defineEmits(['update:modelValue']);

View File

@ -136,7 +136,6 @@ Route::group(['middleware' => 'auth:web'], function (): void {
// ----------------------------------- group ----------------------------------
Route::get('/group', GroupIndexAction::class)->name('group.index');
Route::get('/api/group/{group?}', GroupApiIndexAction::class)->name('api.group');
Route::post('/group/bulkstore', GroupBulkstoreAction::class)->name('group.bulkstore');
// ----------------------------------- course ----------------------------------
@ -151,3 +150,5 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::patch('/formtemplate/{formtemplate}', FormtemplateUpdateAction::class)->name('formtemplate.update');
Route::post('/form', FormStoreAction::class)->name('form.store');
});
Route::get('/api/group/{group?}', GroupApiIndexAction::class)->name('api.group');

View File

@ -3,6 +3,7 @@
namespace Tests\Feature\Form;
use App\Form\Models\Formtemplate;
use App\Group;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
@ -15,13 +16,19 @@ class FormtemplateIndexActionTest extends TestCase
{
$formtemplate = Formtemplate::factory()->create();
$this->login()->loginNami()->withoutExceptionHandling();
$group = Group::factory()->has(Group::factory()->state(['inner_name' => 'child']), 'children')->create(['inner_name' => 'root']);
$this->login()->loginNami(12345, 'pasword', $group)->withoutExceptionHandling();
$this->get(route('formtemplate.index'))
->assertInertiaPath('data.data.0.links', [
'update' => route('formtemplate.update', ['formtemplate' => $formtemplate]),
])
->assertInertiaPath('data.meta.fields.2', [
->assertInertiaPath('data.meta.groups', [
['id' => $group->id, 'name' => 'root'],
['id' => $group->children->first()->id, 'name' => '-- child'],
])
->assertInertiaPath('data.meta.base_url', url(''))
->assertInertiaPath('data.meta.fields.3', [
'id' => 'DropdownField',
'name' => 'Dropdown',
'default' => [
@ -33,7 +40,7 @@ class FormtemplateIndexActionTest extends TestCase
'options' => [],
]
])
->assertInertiaPath('data.meta.fields.4', [
->assertInertiaPath('data.meta.fields.6', [
'id' => 'TextField',
'name' => 'Text',
'default' => [
@ -44,7 +51,7 @@ class FormtemplateIndexActionTest extends TestCase
'required' => false,
]
])
->assertInertiaPath('data.meta.fields.5', [
->assertInertiaPath('data.meta.fields.7', [
'id' => 'TextareaField',
'name' => 'Textarea',
'default' => [