Add condition for email files

This commit is contained in:
philipp lang 2024-04-18 22:15:28 +02:00
parent 4095d218bd
commit adf0ae183e
7 changed files with 179 additions and 3 deletions

View File

@ -0,0 +1,22 @@
<?php
namespace App\Form\Actions;
use App\Form\Models\Form;
use Illuminate\Http\JsonResponse;
use Lorisleiva\Actions\ActionRequest;
use Lorisleiva\Actions\Concerns\AsAction;
class IsDirtyAction
{
use AsAction;
public function handle(Form $form, ActionRequest $request): JsonResponse
{
$form->config = $request->input('config');
return response()->json([
'result' => $form->isDirty('config'),
]);
}
}

View File

@ -62,7 +62,16 @@ class Form extends Model implements HasMedia
->registerMediaConversions(function (Media $media) {
$this->addMediaConversion('square')->fit(Manipulations::FIT_CROP, 400, 400);
});
$this->addMediaCollection('mailattachments');
$this->addMediaCollection('mailattachments')
->withDefaultProperties(fn () => [
'conditions' => [],
])
->withPropertyValidation(fn () => [
'conditions' => 'array',
'conditions.*.field' => 'required',
'conditions.*.comparator' => 'required',
'conditions.*.value' => 'present',
]);
}
/**

View File

@ -47,6 +47,7 @@ class FormResource extends JsonResource
'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()]),
]
];
}

@ -1 +1 @@
Subproject commit 3a7f5587550f27864a14236f2ef9af77e947966c
Subproject commit d2f7f16e88fc3062573fe38e0e9ddef6f2b0ea55

View File

@ -0,0 +1,129 @@
<template>
<ui-note class="mt-2" type="danger" v-if="locked">
Dieses Formular wurde bereits bearbeitet.<br />
Bitte speichere es erst ab und editiere dann die Bedingungen.
</ui-note>
<div v-else>
<div class="mt-2">Datei: {{ value.name }}</div>
<ui-icon-button class="mt-4 mb-2" icon="plus" @click="addCondition">Bedingung einfügen</ui-icon-button>
<div v-for="(condition, index) in conditions" class="grid grid-cols-[1fr_1fr_1fr_max-content] gap-2">
<f-select :options="fieldOptions" v-model="condition.field" :id="`field-${index}`" :name="`field-${index}`" label="Feld"></f-select>
<f-select
:options="comparatorOptions"
:model-value="condition.comparator"
@update:model-value="updateComparator(condition, $event)"
:id="`comparator-${index}`"
:name="`comparator-${index}`"
label="Vergleich"
></f-select>
<f-select
v-if="condition.field && ['isEqual', 'isNotEqual'].includes(condition.comparator)"
:options="getOptions(condition.field)"
v-model="condition.value"
:id="`value-${index}`"
:name="`value-${index}`"
label="Wert"
></f-select>
<f-multipleselect
v-if="condition.field && ['isIn', 'isNotIn'].includes(condition.comparator)"
:options="getOptions(condition.field)"
v-model="condition.value"
:id="`value-${index}`"
:name="`value-${index}`"
label="Wert"
></f-multipleselect>
<ui-action-button tooltip="Löschen" icon="trash" class="btn-danger self-end h-8" @click="conditions.splice(index, 1)"></ui-action-button>
</div>
<ui-icon-button class="mt-4 mb-2" icon="save" @click="save">Speichern</ui-icon-button>
</div>
</template>
<script setup>
import {ref, inject, computed} from 'vue';
const axios = inject('axios');
const emit = defineEmits(['close']);
const props = defineProps({
value: {
required: true,
},
single: {
required: true,
},
});
const comparatorOptions = ref([
{id: 'isEqual', name: 'ist gleich', defaultValue: null},
{id: 'isNotEqual', name: 'ist ungleich', defaultValue: null},
{id: 'isIn', name: 'ist in', defaultValue: []},
{id: 'isNotIn', name: 'ist nicht in', defaultValue: []},
]);
const fields = computed(() => {
const result = [];
props.single.config.sections.forEach((section) => {
section.fields.forEach((field) => {
if (['DropdownField', 'RadioField', 'CheckboxField'].includes(field.type)) {
result.push(field);
}
});
});
return result;
});
function updateComparator(condition, comparator) {
condition.value = comparatorOptions.value.find((c) => c.id === comparator).defaultValue;
condition.comparator = comparator;
}
function getField(fieldName) {
return fields.value.find((f) => f.key === fieldName);
}
function getOptions(fieldName) {
return getField(fieldName).options.map((o) => {
return {id: o, name: o};
});
}
const fieldOptions = computed(() =>
fields.value.map((field) => {
return {id: field.key, name: field.name};
})
);
const conditions = ref('conditions' in props.value.properties ? props.value.properties.conditions : []);
const locked = ref(false);
function addCondition() {
conditions.value.push({
field: null,
comparator: null,
value: null,
});
}
async function save() {
await axios.patch(`/mediaupload/${props.value.id}`, {
properties: {
...props.value.properties,
conditions: conditions.value,
},
});
emit('close');
}
async function checkIfDirty() {
const response = await axios.post(props.single.links.is_dirty, {config: props.single.config});
locked.value = response.data.result;
}
checkIfDirty();
</script>

View File

@ -84,7 +84,14 @@
:parent-id="single.id"
collection="mailattachments"
class="row-span-2"
></f-multiplefiles>
>
<template #buttons="{file, buttonClass, iconClass}">
<a v-tooltip="`Bedingungen`" href="#" :class="[buttonClass, 'bg-blue-200', 'relative']" @click.prevent="fileSettingPopup = file">
<div v-if="file.properties.conditions.length" class="absolute w-2 h-2 -mt-[0.05rem] -ml-[0.05rem] flex-none bg-red-900 rounded-full top-0 left-0"></div>
<ui-sprite src="setting" :class="[iconClass, 'text-blue-800']"></ui-sprite>
</a>
</template>
</f-multiplefiles>
<f-textarea id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2" rows="8" required></f-textarea>
</div>
</div>
@ -95,6 +102,10 @@
</template>
</ui-popup>
<ui-popup v-if="fileSettingPopup !== null" heading="Bedingungen bearbeiten" @close="fileSettingPopup = null">
<file-settings @close="fileSettingPopup = null" :single="single" :value="fileSettingPopup"></file-settings>
</ui-popup>
<page-filter breakpoint="xl">
<f-text id="search" :model-value="getFilter('search')" name="search" label="Suchen …" size="sm" @update:model-value="setFilter('search', $event)"></f-text>
<f-switch id="past" :model-value="getFilter('past')" label="vergangene zeigen" size="sm" @update:model-value="setFilter('past', $event)"></f-switch>
@ -142,6 +153,7 @@ import {ref} from 'vue';
import {indexProps, useIndex} from '../../composables/useInertiaApiIndex.js';
import FormBuilder from '../formtemplate/FormBuilder.vue';
import Participants from './Participants.vue';
import FileSettings from './FileSettings.vue';
const props = defineProps(indexProps);
var {meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFilter, setFilter} = useIndex(props.data, 'form');
@ -149,6 +161,7 @@ var {meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFi
const active = ref(0);
const deleting = ref(null);
const showing = ref(null);
const fileSettingPopup = ref(null);
const tabs = [{title: 'Allgemeines'}, {title: 'Formular'}, {title: 'E-Mail'}, {title: 'Export'}];

View File

@ -30,6 +30,7 @@ use App\Form\Actions\FormtemplateStoreAction;
use App\Form\Actions\FormtemplateUpdateAction;
use App\Form\Actions\FormUpdateAction;
use App\Form\Actions\FormUpdateMetaAction;
use App\Form\Actions\IsDirtyAction;
use App\Form\Actions\ParticipantIndexAction;
use App\Initialize\Actions\InitializeAction;
use App\Initialize\Actions\InitializeFormAction;
@ -160,4 +161,5 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::post('/form', FormStoreAction::class)->name('form.store');
Route::patch('/form/{form}/meta', FormUpdateMetaAction::class)->name('form.update-meta');
Route::get('/form/{form}/participants', ParticipantIndexAction::class)->name('form.participant.index');
Route::post('/form/{form}/is-dirty', IsDirtyAction::class)->name('form.is-dirty');
});