Compare commits
22 Commits
594c45aede
...
e0bd7c5add
Author | SHA1 | Date |
---|---|---|
|
e0bd7c5add | |
|
47cbc3321c | |
|
2984ad9372 | |
|
0ba8c58c76 | |
|
0437511bf1 | |
|
1328f359b2 | |
|
4997895dfb | |
|
4f1c02acac | |
|
ae39c8dd31 | |
|
dbffb4b394 | |
|
1929c8c216 | |
|
21dfc4f0b2 | |
|
5e2427ee81 | |
|
37c8f35b58 | |
|
5310686b9c | |
|
2363be7d61 | |
|
2abe061c4f | |
|
fde1a9d169 | |
|
1f4173caf8 | |
|
6ad639730a | |
|
096e44b767 | |
|
e08ad63313 |
|
@ -8,7 +8,7 @@ yarn-error.log
|
|||
/public/build
|
||||
/public/vendor
|
||||
storage/*.key
|
||||
vendor/
|
||||
/vendor/
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
.vagrant/
|
||||
|
|
|
@ -28,8 +28,8 @@ class FormStoreAction
|
|||
'to' => 'required|date',
|
||||
'registration_from' => 'present|nullable|date',
|
||||
'registration_until' => 'present|nullable|date',
|
||||
'mail_top' => 'nullable|string',
|
||||
'mail_bottom' => 'nullable|string',
|
||||
'mail_top' => 'array',
|
||||
'mail_bottom' => 'array',
|
||||
'header_image' => 'required|exclude',
|
||||
'mailattachments' => 'present|array|exclude',
|
||||
];
|
||||
|
|
|
@ -29,8 +29,8 @@ class FormUpdateAction
|
|||
'to' => 'required|date',
|
||||
'registration_from' => 'present|nullable|date',
|
||||
'registration_until' => 'present|nullable|date',
|
||||
'mail_top' => 'nullable|string',
|
||||
'mail_bottom' => 'nullable|string',
|
||||
'mail_top' => 'array',
|
||||
'mail_bottom' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,13 @@ class IsDirtyAction
|
|||
{
|
||||
use AsAction;
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'config' => 'array|present',
|
||||
];
|
||||
}
|
||||
|
||||
public function handle(Form $form, ActionRequest $request): JsonResponse
|
||||
{
|
||||
$form->config = $request->input('config');
|
||||
|
|
|
@ -70,7 +70,12 @@ class FieldCollection extends Collection
|
|||
|
||||
public function find(Field $givenField): ?Field
|
||||
{
|
||||
return $this->first(fn ($field) => $field->key === $givenField->key);
|
||||
return $this->findByKey($givenField->key);
|
||||
}
|
||||
|
||||
public function findByKey(string $key): ?Field
|
||||
{
|
||||
return $this->first(fn ($field) => $field->key === $key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Editor;
|
||||
|
||||
use App\Form\Models\Participant;
|
||||
use App\Lib\Editor\ConditionResolver;
|
||||
|
||||
class FormConditionResolver extends ConditionResolver
|
||||
{
|
||||
|
||||
private Participant $participant;
|
||||
|
||||
public function forParticipant(Participant $participant): self
|
||||
{
|
||||
$this->participant = $participant;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function filterCondition($mode, $ifs): bool
|
||||
{
|
||||
if (count($ifs) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($ifs as $if) {
|
||||
$field = $this->participant->getFields()->findByKey($if['field']);
|
||||
$matches = $field->matches($if['comparator'], $if['value']);
|
||||
if ($matches && $mode === 'any') {
|
||||
return true;
|
||||
}
|
||||
if (!$matches && $mode === 'all') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode === 'any') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($mode === 'all') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
use App\Form\Matchers\BooleanMatcher;
|
||||
use App\Form\Matchers\Matcher;
|
||||
use App\Form\Models\Form;
|
||||
use App\Form\Models\Participant;
|
||||
use App\Form\Presenters\BooleanPresenter;
|
||||
|
@ -79,4 +81,9 @@ class CheckboxField extends Field
|
|||
{
|
||||
return app(BooleanPresenter::class);
|
||||
}
|
||||
|
||||
public function getMatcher(): Matcher
|
||||
{
|
||||
return app(BooleanMatcher::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
use App\Form\Matchers\Matcher;
|
||||
use App\Form\Matchers\SingleValueMatcher;
|
||||
use App\Form\Models\Form;
|
||||
use App\Form\Models\Participant;
|
||||
use Faker\Generator;
|
||||
|
@ -80,4 +82,9 @@ class DropdownField extends Field
|
|||
public function afterRegistration(Form $form, Participant $participant, array $input): void
|
||||
{
|
||||
}
|
||||
|
||||
public function getMatcher(): Matcher
|
||||
{
|
||||
return app(SingleValueMatcher::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,4 +165,9 @@ abstract class Field extends Data
|
|||
{
|
||||
return $this->key . '_display';
|
||||
}
|
||||
|
||||
public function matches(string $comparator, mixed $value): bool
|
||||
{
|
||||
return $this->getMatcher()->setValue($this->value)->matches($comparator, $value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
use App\Form\Matchers\Matcher;
|
||||
use App\Form\Matchers\SingleValueMatcher;
|
||||
use App\Form\Models\Form;
|
||||
use App\Form\Models\Participant;
|
||||
use Faker\Generator;
|
||||
|
@ -80,4 +82,9 @@ class RadioField extends Field
|
|||
public function afterRegistration(Form $form, Participant $participant, array $input): void
|
||||
{
|
||||
}
|
||||
|
||||
public function getMatcher(): Matcher
|
||||
{
|
||||
return app(SingleValueMatcher::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ class TextField extends Field
|
|||
];
|
||||
}
|
||||
|
||||
public static function default(): string
|
||||
public static function default(): ?string
|
||||
{
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function fake(Generator $faker): array
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Form\Mails;
|
||||
|
||||
use App\Form\Data\FormConfigData;
|
||||
use App\Form\Editor\FormConditionResolver;
|
||||
use App\Form\Models\Participant;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Attachment;
|
||||
|
@ -10,7 +11,6 @@ use Illuminate\Mail\Mailable;
|
|||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ConfirmRegistrationMail extends Mailable
|
||||
{
|
||||
|
@ -18,6 +18,10 @@ class ConfirmRegistrationMail extends Mailable
|
|||
|
||||
public string $fullname;
|
||||
public FormConfigData $config;
|
||||
/** @var array<string, mixed> */
|
||||
public array $topText;
|
||||
/** @var array<string, mixed> */
|
||||
public array $bottomText;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
|
@ -26,8 +30,11 @@ class ConfirmRegistrationMail extends Mailable
|
|||
*/
|
||||
public function __construct(public Participant $participant)
|
||||
{
|
||||
$conditionResolver = app(FormConditionResolver::class)->forParticipant($participant);
|
||||
$this->fullname = $participant->getFields()->getFullname();
|
||||
$this->config = $participant->getConfig();
|
||||
$this->topText = $conditionResolver->makeBlocks($participant->form->mail_top);
|
||||
$this->bottomText = $conditionResolver->makeBlocks($participant->form->mail_bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +68,13 @@ class ConfirmRegistrationMail extends Mailable
|
|||
*/
|
||||
public function attachments()
|
||||
{
|
||||
$conditionResolver = app(FormConditionResolver::class)->forParticipant($this->participant);
|
||||
|
||||
return $this->participant->form->getMedia('mailattachments')
|
||||
->filter(fn ($media) => $conditionResolver->filterCondition(
|
||||
data_get($media->getCustomProperty('conditions'), 'mode', 'all'),
|
||||
data_get($media->getCustomProperty('conditions'), 'ifs', []),
|
||||
))
|
||||
->map(fn ($media) => Attachment::fromStorageDisk($media->disk, $media->getPathRelativeToRoot()))
|
||||
->all();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Matchers;
|
||||
|
||||
class BooleanMatcher extends SingleValueMatcher
|
||||
{
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Matchers;
|
||||
|
||||
abstract class Matcher
|
||||
{
|
||||
|
||||
public mixed $value;
|
||||
|
||||
public function setValue(mixed $value): self
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
abstract public function matches(string $comparator, mixed $value): bool;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Matchers;
|
||||
|
||||
class SingleValueMatcher extends Matcher
|
||||
{
|
||||
|
||||
public function matches(string $comparator, mixed $value): bool
|
||||
{
|
||||
if ($comparator === 'isEqual' && $value === $this->value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($comparator === 'isNotEqual' && $value !== $this->value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($comparator === 'isIn' && in_array($this->value, $value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($comparator === 'isNotIn' && !in_array($this->value, $value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -29,6 +29,8 @@ class Form extends Model implements HasMedia
|
|||
'config' => FormConfigData::class,
|
||||
'meta' => 'json',
|
||||
'description' => 'json',
|
||||
'mail_top' => 'json',
|
||||
'mail_bottom' => 'json',
|
||||
];
|
||||
|
||||
/** @var array<int, string> */
|
||||
|
@ -64,13 +66,17 @@ class Form extends Model implements HasMedia
|
|||
});
|
||||
$this->addMediaCollection('mailattachments')
|
||||
->withDefaultProperties(fn () => [
|
||||
'conditions' => [],
|
||||
'conditions' => [
|
||||
'mode' => 'all',
|
||||
'ifs' => []
|
||||
],
|
||||
])
|
||||
->withPropertyValidation(fn () => [
|
||||
'conditions' => 'array',
|
||||
'conditions.*.field' => 'required',
|
||||
'conditions.*.comparator' => 'required',
|
||||
'conditions.*.value' => 'present',
|
||||
'conditions.mode' => 'required|string|in:all,any',
|
||||
'conditions.ifs' => 'array',
|
||||
'conditions.ifs.*.field' => 'required',
|
||||
'conditions.ifs.*.comparator' => 'required',
|
||||
'conditions.ifs.*.value' => 'present',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -133,11 +139,12 @@ class Form extends Model implements HasMedia
|
|||
public static function booted(): void
|
||||
{
|
||||
static::saving(function (self $model) {
|
||||
if (is_null($model->meta)) {
|
||||
if (is_null(data_get($model->meta, 'active_columns'))) {
|
||||
$model->setAttribute('meta', [
|
||||
'active_columns' => $model->getFields()->count() ? $model->getFields()->take(4)->pluck('key')->toArray() : null,
|
||||
'sorting' => $model->getFields()->count() ? [$model->getFields()->first()->key, 'asc'] : null,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array(data_get($model->meta, 'active_columns'))) {
|
||||
|
@ -145,6 +152,7 @@ class Form extends Model implements HasMedia
|
|||
...$model->meta,
|
||||
'active_columns' => array_values(array_intersect([...$model->getFields()->pluck('key')->toArray(), 'created_at'], $model->meta['active_columns'])),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -77,8 +77,8 @@ class FormResource extends JsonResource
|
|||
'to' => null,
|
||||
'registration_from' => null,
|
||||
'registration_until' => null,
|
||||
'mail_top' => null,
|
||||
'mail_bottom' => null,
|
||||
'mail_top' => [],
|
||||
'mail_bottom' => [],
|
||||
'config' => null,
|
||||
'header_image' => null,
|
||||
'mailattachments' => [],
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Editor;
|
||||
|
||||
abstract class ConditionResolver
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $ifs
|
||||
*/
|
||||
abstract public function filterCondition(string $mode, array $ifs): bool;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $content
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function makeBlocks(array $content): array
|
||||
{
|
||||
return array_filter(data_get($content, 'blocks', []), fn ($block) => $this->filterBlock($block));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function filterBlock(array $block): bool
|
||||
{
|
||||
$mode = data_get($block, 'tunes.condition.mode', 'any');
|
||||
$ifs = data_get($block, 'tunes.condition.ifs', []);
|
||||
|
||||
return $this->filterCondition($mode, $ifs);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ use App\Mailgateway\Types\LocalType;
|
|||
use App\Mailgateway\Types\MailmanType;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Telescope\Telescope;
|
||||
|
||||
|
@ -36,6 +37,8 @@ class AppServiceProvider extends ServiceProvider
|
|||
]));
|
||||
|
||||
app()->extend('media-library-helpers', fn ($p) => $p->put('form', Form::class));
|
||||
|
||||
Blade::componentNamespace('App\\View\\Mail', 'mail-view');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Mail;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Editor extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @param array<int, mixed> $content
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(public array $content)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|\Closure|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('components.mail.editor');
|
||||
}
|
||||
}
|
|
@ -7,16 +7,14 @@ use Database\Factories\Traits\FakesMedia;
|
|||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Tests\Feature\Form\FormtemplateFieldRequest;
|
||||
use Tests\Feature\Form\FormtemplateSectionRequest;
|
||||
use Tests\RequestFactories\EditorRequestFactory;
|
||||
|
||||
/**
|
||||
* @extends Factory<Form>
|
||||
* @method self name(string $name)
|
||||
* @method self from(string $from)
|
||||
* @method self to(string $to)
|
||||
* @method self mailTop(string $content)
|
||||
* @method self mailBottom(string $content)
|
||||
* @method self excerpt(string $excerpt)
|
||||
* @method self description(string $description)
|
||||
* @method self registrationFrom(string|null $date)
|
||||
* @method self registrationUntil(string|null $date)
|
||||
*/
|
||||
|
@ -40,15 +38,15 @@ class FormFactory extends Factory
|
|||
{
|
||||
return [
|
||||
'name' => $this->faker->words(4, true),
|
||||
'description' => $this->faker->text(),
|
||||
'description' => EditorRequestFactory::new()->create(),
|
||||
'excerpt' => $this->faker->words(10, true),
|
||||
'config' => ['sections' => []],
|
||||
'from' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'),
|
||||
'to' => $this->faker->dateTimeBetween('+1 week', '+4 weeks')->format('Y-m-d H:i:s'),
|
||||
'registration_from' => $this->faker->dateTimeBetween('-2 weeks', 'now')->format('Y-m-d H:i:s'),
|
||||
'registration_until' => $this->faker->dateTimeBetween('now', '+2 weeks')->format('Y-m-d H:i:s'),
|
||||
'mail_top' => $this->faker->text(),
|
||||
'mail_bottom' => $this->faker->text(),
|
||||
'mail_top' => EditorRequestFactory::new()->create(),
|
||||
'mail_bottom' => EditorRequestFactory::new()->create(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -75,4 +73,19 @@ class FormFactory extends Factory
|
|||
{
|
||||
return $this->state([str($method)->snake()->toString() => $parameters[0]]);
|
||||
}
|
||||
|
||||
public function mailTop(EditorRequestFactory $factory): self
|
||||
{
|
||||
return $this->state(['mail_top' => $factory->create()]);
|
||||
}
|
||||
|
||||
public function mailBottom(EditorRequestFactory $factory): self
|
||||
{
|
||||
return $this->state(['mail_bottom' => $factory->create()]);
|
||||
}
|
||||
|
||||
public function description(EditorRequestFactory $factory): self
|
||||
{
|
||||
return $this->state(['description' => $factory->create()]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ trait FakesMedia
|
|||
});
|
||||
}
|
||||
|
||||
public function withDocument(string $collection, string $filename, string $content = ''): self
|
||||
public function withDocument(string $collection, string $filename, string $content = '', array $properties = []): self
|
||||
{
|
||||
return $this->afterCreating(function (HasMedia $model) use ($filename, $collection, $content) {
|
||||
return $this->afterCreating(function (HasMedia $model) use ($filename, $collection, $content, $properties) {
|
||||
$pathinfo = pathinfo($filename);
|
||||
|
||||
UploadedFile::fake()->create($filename, $content, 'application/pdf')->storeAs('media-library', $filename, 'temp');
|
||||
|
@ -32,6 +32,7 @@ trait FakesMedia
|
|||
$model->addMediaFromDisk('media-library/' . $filename, 'temp')
|
||||
->usingName($pathinfo['filename'])
|
||||
->usingFileName($pathinfo['basename'])
|
||||
->withCustomProperties($properties)
|
||||
->toMediaCollection($collection);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,25 @@
|
|||
<template>
|
||||
<div>
|
||||
<span v-if="label" class="font-semibold text-gray-400" :class="labelClass(size)">{{ label }}<span v-show="required" class="text-red-800"> *</span></span>
|
||||
<div class="relative w-full h-full">
|
||||
<div :id="id" :class="[defaultFieldClass, fieldClass(size)]"></div>
|
||||
<div v-if="hint" v-tooltip="hint" class="absolute right-0 top-0 mr-2 mt-2">
|
||||
<ui-sprite src="info-button" class="w-5 h-5 text-indigo-200"></ui-sprite>
|
||||
<div>
|
||||
<span v-if="label" class="font-semibold text-gray-400" :class="labelClass(size)">{{ label }}<span v-show="required" class="text-red-800"> *</span></span>
|
||||
<div class="relative w-full h-full">
|
||||
<div :id="id" :class="[defaultFieldClass, fieldClass(size)]"></div>
|
||||
<div v-if="hint" v-tooltip="hint" class="absolute right-0 top-0 mr-2 mt-2">
|
||||
<ui-sprite src="info-button" class="w-5 h-5 text-indigo-200"></ui-sprite>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ui-popup
|
||||
v-if="condition !== null"
|
||||
heading="Bedingungen"
|
||||
@close="
|
||||
condition.resolve(condition.data);
|
||||
condition = null;
|
||||
"
|
||||
>
|
||||
<slot name="conditions" :data="condition.data" :resolve="condition.resolve" :reject="condition.reject"></slot>
|
||||
</ui-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -39,6 +52,11 @@ const props = defineProps({
|
|||
id: {
|
||||
required: true,
|
||||
},
|
||||
conditions: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
hint: {
|
||||
default: null,
|
||||
},
|
||||
|
@ -54,51 +72,145 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
const editor = ref(null);
|
||||
const condition = ref(null);
|
||||
|
||||
async function openPopup(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
new Promise((innerResolve, innerReject) => {
|
||||
condition.value = {
|
||||
resolve: innerResolve,
|
||||
reject: innerReject,
|
||||
data: data,
|
||||
};
|
||||
}).then((data) => {
|
||||
resolve(data);
|
||||
condition.value = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ConditionTune {
|
||||
constructor({api, data, config, block}) {
|
||||
this.api = api;
|
||||
this.data = data || {
|
||||
mode: 'all',
|
||||
ifs: [],
|
||||
};
|
||||
this.config = config;
|
||||
this.block = block;
|
||||
this.wrapper = null;
|
||||
}
|
||||
|
||||
static get isTune() {
|
||||
return true;
|
||||
}
|
||||
|
||||
wrap(blockContent) {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.appendChild(blockContent);
|
||||
this.styleWrapper();
|
||||
return this.wrapper;
|
||||
}
|
||||
|
||||
hasData() {
|
||||
return this.data.ifs.length > 0;
|
||||
}
|
||||
|
||||
styleWrapper() {
|
||||
if (this.hasData()) {
|
||||
this.wrapper.className = 'relative mt-6 mb-6 p-1 border border-blue-200 rounded';
|
||||
if (!this.wrapper.querySelector('.condition-description')) {
|
||||
var tooltip = document.createElement('div');
|
||||
tooltip.className = 'condition-description absolute top-0 left-0 -mt-4 ml-1 h-4 flex px-2 items-center text-xs leading-none bg-blue-200 text-blue-900 rounded-t-lg';
|
||||
tooltip.innerHTML = 'Bedingung';
|
||||
this.wrapper.appendChild(tooltip);
|
||||
}
|
||||
} else {
|
||||
this.wrapper.className = '';
|
||||
if (this.wrapper.querySelector('.condition-description')) {
|
||||
this.wrapper.removeChild(this.wrapper.querySelector('.condition-description'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return {
|
||||
label: 'Bedingungen',
|
||||
closeOnActivate: true,
|
||||
toggle: true,
|
||||
onActivate: async () => {
|
||||
this.data = await openPopup(this.data);
|
||||
this.styleWrapper();
|
||||
this.block.dispatchChange();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
save() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
var tools = {
|
||||
paragraph: {
|
||||
class: Paragraph,
|
||||
shortcut: 'CTRL+P',
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
preserveBlank: true,
|
||||
placeholder: 'Absatz',
|
||||
},
|
||||
},
|
||||
alert: {
|
||||
class: Alert,
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
defaultType: 'primary',
|
||||
},
|
||||
},
|
||||
heading: {
|
||||
class: Header,
|
||||
shortcut: 'CTRL+H',
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
placeholder: 'Überschrift',
|
||||
levels: [2, 3, 4],
|
||||
defaultLevel: 2,
|
||||
},
|
||||
},
|
||||
list: {
|
||||
class: NestedList,
|
||||
shortcut: 'CTRL+L',
|
||||
inlineToolbar: true,
|
||||
},
|
||||
};
|
||||
|
||||
var tunes = [];
|
||||
|
||||
if (props.conditions) {
|
||||
tools.condition = {
|
||||
class: ConditionTune,
|
||||
};
|
||||
tunes.push('condition');
|
||||
}
|
||||
|
||||
editor.value = new EditorJS({
|
||||
placeholder: props.placeholder,
|
||||
holder: props.id,
|
||||
minHeight: 0,
|
||||
defaultBlock: 'paragraph',
|
||||
data: JSON.parse(JSON.stringify(props.modelValue)),
|
||||
tools: {
|
||||
paragraph: {
|
||||
class: Paragraph,
|
||||
shortcut: 'CTRL+P',
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
preserveBlank: true,
|
||||
placeholder: 'Absatz',
|
||||
},
|
||||
},
|
||||
alert: {
|
||||
class: Alert,
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
defaultType: 'primary',
|
||||
},
|
||||
},
|
||||
heading: {
|
||||
class: Header,
|
||||
shortcut: 'CTRL+H',
|
||||
inlineToolbar: true,
|
||||
config: {
|
||||
placeholder: 'Überschrift',
|
||||
levels: [2, 3, 4],
|
||||
defaultLevel: 2,
|
||||
},
|
||||
},
|
||||
list: {
|
||||
class: NestedList,
|
||||
shortcut: 'CTRL+L',
|
||||
inlineToolbar: true,
|
||||
},
|
||||
},
|
||||
tunes: tunes,
|
||||
tools: tools,
|
||||
onChange: debounce(async (api, event) => {
|
||||
const data = await editor.value.save();
|
||||
console.log(data);
|
||||
emit('update:modelValue', data);
|
||||
}, 500),
|
||||
}, 200),
|
||||
onPopup: () => {
|
||||
console.log('opened');
|
||||
},
|
||||
});
|
||||
await editor.value.isReady;
|
||||
console.log('Editor is ready');
|
||||
|
|
|
@ -5,21 +5,29 @@
|
|||
</ui-note>
|
||||
|
||||
<div v-else>
|
||||
<div class="mt-2">Datei: {{ value.name }}</div>
|
||||
<f-select id="mode" v-model="inner.mode" :options="modeOptions" name="mode" label="Modus"></f-select>
|
||||
|
||||
<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" :key="index" class="grid grid-cols-[1fr_1fr_1fr_max-content] gap-2">
|
||||
<f-select :id="`field-${index}`" v-model="condition.field" :options="fieldOptions" :name="`field-${index}`" label="Feld"></f-select>
|
||||
<div v-for="(condition, index) in inner.ifs" :key="index" class="grid grid-cols-[1fr_1fr_1fr_max-content] gap-2">
|
||||
<f-select
|
||||
:id="`field-${index}`"
|
||||
:model-value="condition.field"
|
||||
:options="fieldOptions"
|
||||
:name="`field-${index}`"
|
||||
label="Feld"
|
||||
@update:model-value="update(index, 'field', $event)"
|
||||
></f-select>
|
||||
<f-select
|
||||
:id="`comparator-${index}`"
|
||||
:options="comparatorOptions"
|
||||
:model-value="condition.comparator"
|
||||
:name="`comparator-${index}`"
|
||||
label="Vergleich"
|
||||
@update:model-value="updateComparator(condition, $event)"
|
||||
@update:model-value="update(index, 'comparator', $event)"
|
||||
></f-select>
|
||||
<f-select
|
||||
v-if="condition.field && ['isEqual', 'isNotEqual'].includes(condition.comparator)"
|
||||
v-if="condition.field && ['isEqual', 'isNotEqual'].includes(condition.comparator) && ['RadioField', 'DropdownField'].includes(getField(condition.field).type)"
|
||||
:id="`value-${index}`"
|
||||
v-model="condition.value"
|
||||
:options="getOptions(condition.field)"
|
||||
|
@ -27,14 +35,21 @@
|
|||
label="Wert"
|
||||
></f-select>
|
||||
<f-multipleselect
|
||||
v-if="condition.field && ['isIn', 'isNotIn'].includes(condition.comparator)"
|
||||
v-if="condition.field && ['isIn', 'isNotIn'].includes(condition.comparator) && ['RadioField', 'DropdownField'].includes(getField(condition.field).type)"
|
||||
:id="`value-${index}`"
|
||||
v-model="condition.value"
|
||||
:options="getOptions(condition.field)"
|
||||
: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>
|
||||
<f-switch
|
||||
v-if="condition.field && condition.comparator && ['CheckboxField'].includes(getField(condition.field).type)"
|
||||
:id="`value-${index}`"
|
||||
v-model="condition.value"
|
||||
:name="`value-${index}`"
|
||||
label="Wert"
|
||||
></f-switch>
|
||||
<ui-action-button tooltip="Löschen" icon="trash" class="btn-danger self-end h-8" @click="inner.ifs.splice(index, 1)"></ui-action-button>
|
||||
</div>
|
||||
|
||||
<ui-icon-button class="mt-4 mb-2" icon="save" @click="save">Speichern</ui-icon-button>
|
||||
|
@ -44,7 +59,7 @@
|
|||
<script setup>
|
||||
import {ref, inject, computed} from 'vue';
|
||||
const axios = inject('axios');
|
||||
const emit = defineEmits(['close']);
|
||||
const emit = defineEmits(['save']);
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
|
@ -56,10 +71,15 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
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: []},
|
||||
{id: 'isEqual', name: 'ist gleich', defaultValue: {DropdownField: null, RadioField: null, CheckboxField: false}},
|
||||
{id: 'isNotEqual', name: 'ist ungleich', defaultValue: {DropdownField: null, RadioField: null, CheckboxField: false}},
|
||||
{id: 'isIn', name: 'ist in', defaultValue: {DropdownField: [], RadioField: [], CheckboxField: false}},
|
||||
{id: 'isNotIn', name: 'ist nicht in', defaultValue: {DropdownField: [], RadioField: [], CheckboxField: false}},
|
||||
]);
|
||||
|
||||
const modeOptions = ref([
|
||||
{id: 'all', name: 'alle Bedingungen müssen zutreffen'},
|
||||
{id: 'any', name: 'mindestens eine Bedingung muss zutreffen'},
|
||||
]);
|
||||
|
||||
const fields = computed(() => {
|
||||
|
@ -75,9 +95,23 @@ const fields = computed(() => {
|
|||
return result;
|
||||
});
|
||||
|
||||
function updateComparator(condition, comparator) {
|
||||
condition.value = comparatorOptions.value.find((c) => c.id === comparator).defaultValue;
|
||||
condition.comparator = comparator;
|
||||
function update(index, key, value) {
|
||||
if (key === 'comparator') {
|
||||
var old = inner.value.ifs[index];
|
||||
inner.value.ifs[index] = {
|
||||
field: old.field,
|
||||
comparator: value,
|
||||
value: old.field ? comparatorOptions.value.find((c) => c.id === value).defaultValue[getField(old.field).type] : null,
|
||||
};
|
||||
}
|
||||
if (key === 'field') {
|
||||
var old = inner.value.ifs[index];
|
||||
inner.value.ifs[index] = {
|
||||
field: value,
|
||||
comparator: null,
|
||||
value: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getField(fieldName) {
|
||||
|
@ -96,12 +130,12 @@ const fieldOptions = computed(() =>
|
|||
})
|
||||
);
|
||||
|
||||
const conditions = ref('conditions' in props.value.properties ? props.value.properties.conditions : []);
|
||||
const inner = ref(JSON.parse(JSON.stringify(props.value)));
|
||||
|
||||
const locked = ref(false);
|
||||
|
||||
function addCondition() {
|
||||
conditions.value.push({
|
||||
inner.value.ifs.push({
|
||||
field: null,
|
||||
comparator: null,
|
||||
value: null,
|
||||
|
@ -109,14 +143,7 @@ function addCondition() {
|
|||
}
|
||||
|
||||
async function save() {
|
||||
await axios.patch(`/mediaupload/${props.value.id}`, {
|
||||
properties: {
|
||||
...props.value.properties,
|
||||
conditions: conditions.value,
|
||||
},
|
||||
});
|
||||
|
||||
emit('close');
|
||||
emit('save', inner.value);
|
||||
}
|
||||
|
||||
async function checkIfDirty() {
|
|
@ -63,7 +63,7 @@
|
|||
></f-textarea>
|
||||
<f-editor id="description" v-model="single.description" name="description" label="Beschreibung" rows="10" required></f-editor>
|
||||
</div>
|
||||
<div v-show="active === 1">
|
||||
<div v-if="active === 1">
|
||||
<ui-note class="mt-2"> Sobald sich der erste Teilnehmer für die Veranstaltung angemeldet hat, kann dieses Formular nicht mehr geändert werden. </ui-note>
|
||||
<form-builder v-model="single.config" :meta="meta"></form-builder>
|
||||
</div>
|
||||
|
@ -74,7 +74,19 @@
|
|||
Die Anrede ("Hallo Max Mustermann") wird automatisch an den Anfang gesetzt.<br />
|
||||
Außerdem kannst du Dateien hochladen, die automatisch mit angehangen werden.
|
||||
</ui-note>
|
||||
<f-textarea id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" rows="8" required></f-textarea>
|
||||
<div>
|
||||
<ui-tabs v-model="activeMailTab" :entries="mailTabs"></ui-tabs>
|
||||
<f-editor v-if="activeMailTab === 0" id="mail_top" v-model="single.mail_top" name="mail_top" label="E-Mail-Teil 1" rows="8" conditions required>
|
||||
<template #conditions="{data, resolve}">
|
||||
<conditions :single="single" :value="data" @save="resolve"> </conditions>
|
||||
</template>
|
||||
</f-editor>
|
||||
<f-editor v-if="activeMailTab === 1" id="mail_bottom" v-model="single.mail_bottom" name="mail_bottom" label="E-Mail-Teil 2" rows="8" conditions required>
|
||||
<template #conditions="{data, resolve}">
|
||||
<conditions :single="single" :value="data" @save="resolve"> </conditions>
|
||||
</template>
|
||||
</f-editor>
|
||||
</div>
|
||||
<f-multiplefiles
|
||||
id="mailattachments"
|
||||
v-model="single.mailattachments"
|
||||
|
@ -87,12 +99,11 @@
|
|||
>
|
||||
<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>
|
||||
<div v-if="file.properties.conditions.ifs.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>
|
||||
<template #actions>
|
||||
|
@ -102,8 +113,8 @@
|
|||
</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 v-if="fileSettingPopup !== null" :heading="`Bedingungen für Datei ${fileSettingPopup.name}`" @close="fileSettingPopup = null">
|
||||
<conditions :single="single" :value="fileSettingPopup.properties.conditions" @save="saveFileConditions"> </conditions>
|
||||
</ui-popup>
|
||||
|
||||
<page-filter breakpoint="xl">
|
||||
|
@ -149,27 +160,44 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import {ref, inject} 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';
|
||||
import Conditions from './Conditions.vue';
|
||||
import {useToast} from 'vue-toastification';
|
||||
|
||||
const props = defineProps(indexProps);
|
||||
var {meta, data, reloadPage, create, single, edit, cancel, submit, remove, getFilter, setFilter} = useIndex(props.data, 'form');
|
||||
const axios = inject('axios');
|
||||
const toast = useToast();
|
||||
|
||||
const active = ref(0);
|
||||
const activeMailTab = 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'}];
|
||||
const mailTabs = [{title: 'vor Daten'}, {title: 'nach Daten'}];
|
||||
|
||||
function setTemplate(template) {
|
||||
active.value = 0;
|
||||
single.value.config = template.config;
|
||||
}
|
||||
|
||||
async function saveFileConditions(conditions) {
|
||||
await axios.patch(`/mediaupload/${fileSettingPopup.value.id}`, {
|
||||
properties: {
|
||||
...fileSettingPopup.value.properties,
|
||||
conditions: conditions,
|
||||
},
|
||||
});
|
||||
|
||||
fileSettingPopup.value = null;
|
||||
toast.success('Datei aktualisiert');
|
||||
}
|
||||
|
||||
function showParticipants(form) {
|
||||
showing.value = form;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
@foreach ($content as $block)
|
||||
|
||||
@if ($block['type'] === 'paragraph')
|
||||
{!! $block['data']['text'] !!}
|
||||
@endif
|
||||
|
||||
@if ($block['type'] === 'heading' && data_get($block, 'data.level', 2) === 2)
|
||||
## {!! data_get($block, 'data.text') !!}
|
||||
@endif
|
||||
|
||||
@if ($block['type'] === 'heading' && data_get($block, 'data.level', 2) === 3)
|
||||
### {!! data_get($block, 'data.text') !!}
|
||||
@endif
|
||||
|
||||
@if ($block['type'] === 'heading' && data_get($block, 'data.level', 2) === 4)
|
||||
#### {!! data_get($block, 'data.text') !!}
|
||||
@endif
|
||||
|
||||
@if ($block['type'] === 'list' && data_get($block, 'data.style', 'unordered') === 'unordered')
|
||||
{!! collect(data_get($block, 'data.items', []))->map(fn ($item) => '* '.$item['content'])->implode("\n") !!}
|
||||
@endif
|
||||
|
||||
@if ($block['type'] === 'list' && data_get($block, 'data.style', 'unordered') === 'ordered')
|
||||
{!! collect(data_get($block, 'data.items', []))->map(fn ($item) => '1. '.$item['content'])->implode("\n") !!}
|
||||
@endif
|
||||
|
||||
@if ($block['type'] === 'alert')
|
||||
<x-mail::panel :type="$block['data']['type']">{!! data_get($block, 'data.message') !!}</x-mail::panel>
|
||||
@endif
|
||||
|
||||
@endforeach
|
|
@ -1,7 +1,8 @@
|
|||
@component('mail::message')
|
||||
<x-mail::message>
|
||||
|
||||
# Hallo {{$fullname}},
|
||||
|
||||
{{ $participant->form->mail_top }}
|
||||
<x-mail-view::editor :content="$topText"></x-mail-view::editor>
|
||||
|
||||
# Deine Daten
|
||||
|
||||
|
@ -12,6 +13,6 @@
|
|||
@endforeach
|
||||
@endforeach
|
||||
|
||||
{{ $participant->form->mail_bottom }}
|
||||
<x-mail-view::editor :content="$bottomText"></x-mail-view::editor>
|
||||
|
||||
@endcomponent
|
||||
</x-mail::message>
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
@props([
|
||||
'url',
|
||||
'color' => 'primary',
|
||||
'align' => 'center',
|
||||
])
|
||||
<table class="action" align="{{ $align }}" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<td align="{{ $align }}">
|
||||
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<td align="{{ $align }}">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ $url }}" class="button button-{{ $color ?? 'primary' }}" target="_blank" rel="noopener">{{ $slot }}</a>
|
||||
<a href="{{ $url }}" class="button button-{{ $color }}" target="_blank" rel="noopener">{{ $slot }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
@props(['url'])
|
||||
<tr>
|
||||
<td class="header">
|
||||
<a href="{{ $url }}" style="display: inline-block;">
|
||||
<img src="{{ url('/img/logo.png') }}" class="logo" alt="Stamm Silva Logo">
|
||||
@if (trim($slot) === 'Laravel')
|
||||
<img src="https://laravel.com/img/notification-logo.png" class="logo" alt="Laravel Logo">
|
||||
@else
|
||||
{{ $slot }}
|
||||
@endif
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>{{ config('app.name') }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="color-scheme" content="light">
|
||||
|
@ -33,7 +34,7 @@ width: 100% !important;
|
|||
|
||||
<!-- Email Body -->
|
||||
<tr>
|
||||
<td class="body" width="100%" cellpadding="0" cellspacing="0">
|
||||
<td class="body" width="100%" cellpadding="0" cellspacing="0" style="border: hidden !important;">
|
||||
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<!-- Body content -->
|
||||
<tr>
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
@component('mail::layout')
|
||||
<x-mail::layout>
|
||||
{{-- Header --}}
|
||||
@slot('header')
|
||||
@component('mail::header', ['url' => 'https://stamm-silva.de'])
|
||||
<x-slot:header>
|
||||
<x-mail::header :url="config('app.url')">
|
||||
{{ config('app.name') }}
|
||||
@endcomponent
|
||||
@endslot
|
||||
</x-mail::header>
|
||||
</x-slot:header>
|
||||
|
||||
{{-- Body --}}
|
||||
{{ $slot }}
|
||||
|
||||
{{-- Subcopy --}}
|
||||
@isset($subcopy)
|
||||
@slot('subcopy')
|
||||
@component('mail::subcopy')
|
||||
<x-slot:subcopy>
|
||||
<x-mail::subcopy>
|
||||
{{ $subcopy }}
|
||||
@endcomponent
|
||||
@endslot
|
||||
</x-mail::subcopy>
|
||||
</x-slot:subcopy>
|
||||
@endisset
|
||||
|
||||
{{-- Footer --}}
|
||||
@slot('footer')
|
||||
@component('mail::footer')
|
||||
© {{ date('Y') }} Stamm Silva.
|
||||
@endcomponent
|
||||
@endslot
|
||||
@endcomponent
|
||||
<x-slot:footer>
|
||||
<x-mail::footer>
|
||||
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
|
||||
</x-mail::footer>
|
||||
</x-slot:footer>
|
||||
</x-mail::layout>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<table class="panel" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<table class="panel panel-{{$type ?? 'default'}}" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="panel-content">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
body,
|
||||
body *:not(html):not(style):not(br):not(tr):not(code) {
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
@ -283,6 +282,46 @@ img {
|
|||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.panel.panel-danger {
|
||||
border-left: #cf3917 solid 4px;
|
||||
}
|
||||
.panel.panel-danger .panel-content {
|
||||
background-color: #ff8282;
|
||||
}
|
||||
.panel.panel-danger .panel-content p {
|
||||
color: #250000;
|
||||
}
|
||||
|
||||
.panel.panel-warning {
|
||||
border-left: #cfb017 solid 4px;
|
||||
}
|
||||
.panel.panel-warning .panel-content {
|
||||
background-color: #ffeb82;
|
||||
}
|
||||
.panel.panel-warning .panel-content p {
|
||||
color: #252100;
|
||||
}
|
||||
|
||||
.panel.panel-success {
|
||||
border-left: #19cf17 solid 4px;
|
||||
}
|
||||
.panel.panel-success .panel-content {
|
||||
background-color: #82ff87;
|
||||
}
|
||||
.panel.panel-success .panel-content p {
|
||||
color: #002500;
|
||||
}
|
||||
|
||||
.panel.panel-info {
|
||||
border-left: #3417cf solid 4px;
|
||||
}
|
||||
.panel.panel-info .panel-content {
|
||||
background-color: #9082ff;
|
||||
}
|
||||
.panel.panel-info .panel-content p {
|
||||
color: #050025;
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
|
||||
.break-all {
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
@component('mail::layout')
|
||||
<x-mail::layout>
|
||||
{{-- Header --}}
|
||||
@slot('header')
|
||||
@component('mail::header', ['url' => config('app.url')])
|
||||
<x-slot:header>
|
||||
<x-mail::header :url="config('app.url')">
|
||||
{{ config('app.name') }}
|
||||
@endcomponent
|
||||
@endslot
|
||||
</x-mail::header>
|
||||
</x-slot:header>
|
||||
|
||||
{{-- Body --}}
|
||||
{{ $slot }}
|
||||
|
||||
{{-- Subcopy --}}
|
||||
@isset($subcopy)
|
||||
@slot('subcopy')
|
||||
@component('mail::subcopy')
|
||||
<x-slot:subcopy>
|
||||
<x-mail::subcopy>
|
||||
{{ $subcopy }}
|
||||
@endcomponent
|
||||
@endslot
|
||||
</x-mail::subcopy>
|
||||
</x-slot:subcopy>
|
||||
@endisset
|
||||
|
||||
{{-- Footer --}}
|
||||
@slot('footer')
|
||||
@component('mail::footer')
|
||||
<x-slot:footer>
|
||||
<x-mail::footer>
|
||||
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
|
||||
@endcomponent
|
||||
@endslot
|
||||
@endcomponent
|
||||
</x-mail::footer>
|
||||
</x-slot:footer>
|
||||
</x-mail::layout>
|
||||
|
|
|
@ -9,6 +9,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|||
use Illuminate\Support\Facades\Storage;
|
||||
use Tests\EndToEndTestCase;
|
||||
use Tests\Feature\Form\FormtemplateSectionRequest;
|
||||
use Tests\RequestFactories\EditorRequestFactory;
|
||||
|
||||
class FormApiListActionTest extends FormTestCase
|
||||
{
|
||||
|
@ -24,7 +25,7 @@ class FormApiListActionTest extends FormTestCase
|
|||
->name('lala 2')
|
||||
->excerpt('fff')
|
||||
->withImage('headerImage', 'lala-2.jpg')
|
||||
->description('desc')
|
||||
->description(EditorRequestFactory::new()->text(10, 'desc'))
|
||||
->from('2023-05-05')
|
||||
->to('2023-06-07')
|
||||
->sections([FormtemplateSectionRequest::new()->name('sname')])
|
||||
|
@ -37,7 +38,7 @@ class FormApiListActionTest extends FormTestCase
|
|||
->assertJsonPath('data.0.config.sections.0.name', 'sname')
|
||||
->assertJsonPath('data.0.id', $form->id)
|
||||
->assertJsonPath('data.0.excerpt', 'fff')
|
||||
->assertJsonPath('data.0.description', 'desc')
|
||||
->assertJsonPath('data.0.description.blocks.0.data.text', 'desc')
|
||||
->assertJsonPath('data.0.slug', 'lala-2')
|
||||
->assertJsonPath('data.0.image', $form->getMedia('headerImage')->first()->getFullUrl('square'))
|
||||
->assertJsonPath('data.0.dates', '05.05.2023 - 07.06.2023')
|
||||
|
@ -57,7 +58,7 @@ class FormApiListActionTest extends FormTestCase
|
|||
->create();
|
||||
|
||||
sleep(1);
|
||||
$this->get('/api/form?perPage=15')->assertJsonPath('data.0.config.sections.0.fields.0.value', '');
|
||||
$this->get('/api/form?perPage=15')->assertJsonPath('data.0.config.sections.0.fields.0.value', null);
|
||||
}
|
||||
|
||||
public function testItDisplaysRemoteGroups(): void
|
||||
|
|
|
@ -7,6 +7,7 @@ use App\Form\Models\Formtemplate;
|
|||
use App\Form\Models\Participant;
|
||||
use Carbon\Carbon;
|
||||
use Tests\Feature\Form\FormtemplateSectionRequest;
|
||||
use Tests\RequestFactories\EditorRequestFactory;
|
||||
|
||||
class FormIndexActionTest extends FormTestCase
|
||||
{
|
||||
|
@ -19,11 +20,11 @@ class FormIndexActionTest extends FormTestCase
|
|||
$form = Form::factory()
|
||||
->name('lala')
|
||||
->excerpt('fff')
|
||||
->description('desc')
|
||||
->description(EditorRequestFactory::new()->text(10, 'desc'))
|
||||
->from('2023-05-05')
|
||||
->to('2023-06-07')
|
||||
->mailTop('Guten Tag')
|
||||
->mailBottom('Cheers')
|
||||
->mailTop(EditorRequestFactory::new()->text(10, 'Guten Tag'))
|
||||
->mailBottom(EditorRequestFactory::new()->text(10, 'Cheers'))
|
||||
->registrationFrom('2023-05-06 04:00:00')
|
||||
->registrationUntil('2023-04-01 05:00:00')
|
||||
->sections([FormtemplateSectionRequest::new()->name('sname')->fields([$this->textField()])])
|
||||
|
@ -37,9 +38,9 @@ class FormIndexActionTest extends FormTestCase
|
|||
->assertInertiaPath('data.data.0.config.sections.0.name', 'sname')
|
||||
->assertInertiaPath('data.data.0.id', $form->id)
|
||||
->assertInertiaPath('data.data.0.excerpt', 'fff')
|
||||
->assertInertiaPath('data.data.0.description', 'desc')
|
||||
->assertInertiaPath('data.data.0.mail_top', 'Guten Tag')
|
||||
->assertInertiaPath('data.data.0.mail_bottom', 'Cheers')
|
||||
->assertInertiaPath('data.data.0.description.blocks.0.data.text', 'desc')
|
||||
->assertInertiaPath('data.data.0.mail_top.blocks.0.data.text', 'Guten Tag')
|
||||
->assertInertiaPath('data.data.0.mail_bottom.blocks.0.data.text', 'Cheers')
|
||||
->assertInertiaPath('data.data.0.from_human', '05.05.2023')
|
||||
->assertInertiaPath('data.data.0.to_human', '07.06.2023')
|
||||
->assertInertiaPath('data.data.0.from', '2023-05-05')
|
||||
|
|
|
@ -6,7 +6,9 @@ use App\Form\Enums\SpecialType;
|
|||
use App\Form\Mails\ConfirmRegistrationMail;
|
||||
use App\Form\Models\Form;
|
||||
use App\Form\Models\Participant;
|
||||
use Generator;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\RequestFactories\EditorRequestFactory;
|
||||
|
||||
class FormRegisterMailTest extends FormTestCase
|
||||
{
|
||||
|
@ -25,7 +27,7 @@ class FormRegisterMailTest extends FormTestCase
|
|||
])
|
||||
])
|
||||
|
||||
->mailTop('mail top')->mailBottom('mail bottom')
|
||||
->mailTop(EditorRequestFactory::new()->text(10, 'mail top'))->mailBottom(EditorRequestFactory::new()->text(11, 'mail bottom'))
|
||||
)
|
||||
->data(['vorname' => 'Max', 'nachname' => 'Muster'])
|
||||
->create();
|
||||
|
@ -74,4 +76,216 @@ class FormRegisterMailTest extends FormTestCase
|
|||
$mail->assertHasAttachedData('content1', 'beispiel.pdf', ['mime' => 'application/pdf']);
|
||||
$mail->assertHasAttachedData('content2', 'beispiel2.pdf', ['mime' => 'application/pdf']);
|
||||
}
|
||||
|
||||
public function blockDataProvider(): Generator
|
||||
{
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => []],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'any', 'ifs' => []],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'any', 'ifs' => []],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'any', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A']
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'any', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B']
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'any', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'],
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A']
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'any', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'],
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A']
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'B'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B'],
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'A']
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'B'],
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => 'B']
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'B'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isNotEqual', 'value' => 'A']
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'B'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isIn', 'value' => ['A']]
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isNotIn', 'value' => ['B']]
|
||||
]],
|
||||
$this->dropdownField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isNotIn', 'value' => ['B']]
|
||||
]],
|
||||
$this->radioField('fieldkey')->options(['A', 'B']),
|
||||
['fieldkey' => 'A'],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => true]
|
||||
]],
|
||||
$this->checkboxField('fieldkey'),
|
||||
['fieldkey' => true],
|
||||
true,
|
||||
];
|
||||
|
||||
yield [
|
||||
['mode' => 'all', 'ifs' => [
|
||||
['field' => 'fieldkey', 'comparator' => 'isEqual', 'value' => false]
|
||||
]],
|
||||
$this->checkboxField('fieldkey'),
|
||||
['fieldkey' => true],
|
||||
false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider blockDataProvider
|
||||
* @param array<string, mixed> $conditions
|
||||
* @param array<string, mixed> $participantValues
|
||||
*/
|
||||
public function testItFiltersForBlockConditions(array $conditions, FormtemplateFieldRequest $field, array $participantValues, bool $result): void
|
||||
{
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
|
||||
$participant = Participant::factory()->for(
|
||||
Form::factory()
|
||||
->fields([
|
||||
$field,
|
||||
$this->textField('firstname')->specialType(SpecialType::FIRSTNAME),
|
||||
$this->textField('lastname')->specialType(SpecialType::LASTNAME),
|
||||
])
|
||||
->mailTop(EditorRequestFactory::new()->text(10, '::content::', $conditions))
|
||||
)
|
||||
->data(['firstname' => 'Max', 'lastname' => 'Muster', ...$participantValues])
|
||||
->create();
|
||||
|
||||
$mail = new ConfirmRegistrationMail($participant);
|
||||
if ($result) {
|
||||
$mail->assertSeeInText('::content::');
|
||||
} else {
|
||||
$mail->assertDontSeeInText('::content::');
|
||||
}
|
||||
}
|
||||
|
||||
public function testItSendsEmailWhenMailTopIsEmpty(): void
|
||||
{
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
|
||||
$participant = Participant::factory()->for(
|
||||
Form::factory()
|
||||
->fields([
|
||||
$this->textField('firstname')->specialType(SpecialType::FIRSTNAME),
|
||||
$this->textField('lastname')->specialType(SpecialType::LASTNAME),
|
||||
])->state(['mail_top' => []])
|
||||
)
|
||||
->data(['firstname' => 'Max', 'lastname' => 'Muster'])
|
||||
->create();
|
||||
|
||||
$mail = new ConfirmRegistrationMail($participant);
|
||||
$mail->assertSeeInText('Max');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider blockDataProvider
|
||||
* @param array<string, mixed> $conditions
|
||||
* @param array<string, mixed> $participantValues
|
||||
*/
|
||||
public function testItFiltersForAttachments(array $conditions, FormtemplateFieldRequest $field, array $participantValues, bool $result): void
|
||||
{
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
|
||||
$participant = Participant::factory()->for(
|
||||
Form::factory()
|
||||
->fields([
|
||||
$field,
|
||||
$this->textField('firstname')->specialType(SpecialType::FIRSTNAME),
|
||||
$this->textField('lastname')->specialType(SpecialType::LASTNAME),
|
||||
])
|
||||
->withDocument('mailattachments', 'beispiel.pdf', 'content', ['conditions' => $conditions])
|
||||
)
|
||||
->data(['firstname' => 'Max', 'lastname' => 'Muster', ...$participantValues])
|
||||
->create();
|
||||
|
||||
$mail = new ConfirmRegistrationMail($participant);
|
||||
$mail->assertSeeInHtml('Daten');
|
||||
if ($result) {
|
||||
$this->assertTrue($mail->hasAttachedData('content', 'beispiel.pdf', ['mime' => 'application/pdf']));
|
||||
} else {
|
||||
$this->assertFalse($mail->hasAttachedData('content', 'beispiel.pdf', ['mime' => 'application/pdf']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ use Worksome\RequestFactories\RequestFactory;
|
|||
* @method self from(string $date)
|
||||
* @method self to(string $date)
|
||||
* @method self description(?EditorRequestFactory $description)
|
||||
* @method self mailTop(string $content)
|
||||
* @method self mailBottom(string $content)
|
||||
* @method self mailTop(?EditorRequestFactory $content)
|
||||
* @method self mailBottom(?EditorRequestFactory $content)
|
||||
* @method self excerpt(string $description)
|
||||
* @method self registrationFrom(string|null $date)
|
||||
* @method self registrationUntil(string|null $date)
|
||||
|
@ -40,8 +40,8 @@ class FormRequest extends RequestFactory
|
|||
'to' => $this->faker->dateTime()->format('Y-m-d H:i:s'),
|
||||
'registration_from' => $this->faker->dateTime()->format('Y-m-d H:i:s'),
|
||||
'registration_until' => $this->faker->dateTime()->format('Y-m-d H:i:s'),
|
||||
'mail_top' => $this->faker->text(),
|
||||
'mail_bottom' => $this->faker->text(),
|
||||
'mail_top' => EditorRequestFactory::new()->create(),
|
||||
'mail_bottom' => EditorRequestFactory::new()->create(),
|
||||
'header_image' => $this->getHeaderImagePayload(str()->uuid() . '.jpg'),
|
||||
'mailattachments' => [],
|
||||
];
|
||||
|
|
|
@ -26,8 +26,8 @@ class FormStoreActionTest extends FormTestCase
|
|||
->description($description)
|
||||
->excerpt('avff')
|
||||
->registrationFrom('2023-05-04 01:00:00')->registrationUntil('2023-07-07 01:00:00')->from('2023-07-07')->to('2023-07-08')
|
||||
->mailTop('Guten Tag')
|
||||
->mailBottom('Viele Grüße')
|
||||
->mailTop(EditorRequestFactory::new()->text(11, 'lala'))
|
||||
->mailBottom(EditorRequestFactory::new()->text(12, 'lalab'))
|
||||
->headerImage('htzz.jpg')
|
||||
->sections([FormtemplateSectionRequest::new()->name('sname')->fields([$this->textField()->namiType(NamiType::BIRTHDAY)->forMembers(false)->hint('hhh')])])
|
||||
->fake();
|
||||
|
@ -39,8 +39,8 @@ class FormStoreActionTest extends FormTestCase
|
|||
$this->assertEquals('formname', $form->name);
|
||||
$this->assertEquals('avff', $form->excerpt);
|
||||
$this->assertEquals($description->paragraphBlock(10, 'Lorem'), $form->description);
|
||||
$this->assertEquals('Guten Tag', $form->mail_top);
|
||||
$this->assertEquals('Viele Grüße', $form->mail_bottom);
|
||||
$this->assertEquals(json_decode('{"time":1,"blocks":[{"id":11,"type":"paragraph","data":{"text":"lala"},"tunes":{"condition":{"mode":"all","ifs":[]}}}],"version":"1.0"}', true), $form->mail_top);
|
||||
$this->assertEquals(json_decode('{"time":1,"blocks":[{"id":12,"type":"paragraph","data":{"text":"lalab"},"tunes":{"condition":{"mode":"all","ifs":[]}}}],"version":"1.0"}', true), $form->mail_bottom);
|
||||
$this->assertEquals('2023-05-04 01:00', $form->registration_from->format('Y-m-d H:i'));
|
||||
$this->assertEquals('2023-07-07 01:00', $form->registration_until->format('Y-m-d H:i'));
|
||||
$this->assertEquals('2023-07-07', $form->from->format('Y-m-d'));
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace Tests\Feature\Form;
|
|||
|
||||
use App\Form\Models\Form;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class FormUpdateActionTest extends FormTestCase
|
||||
{
|
||||
|
@ -48,4 +47,22 @@ class FormUpdateActionTest extends FormTestCase
|
|||
$this->patchJson(route('form.update', ['form' => $form]), $payload)->assertSessionDoesntHaveErrors()->assertOk();
|
||||
$this->assertEquals(['firstname'], $form->fresh()->meta['active_columns']);
|
||||
}
|
||||
|
||||
public function testItUpdatesActiveColumnsWhenFieldsAdded(): void
|
||||
{
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
$form = Form::factory()
|
||||
->sections([FormtemplateSectionRequest::new()->fields([])])
|
||||
->create();
|
||||
$payload = FormRequest::new()->sections([
|
||||
FormtemplateSectionRequest::new()->fields([
|
||||
$this->textField('firstname'),
|
||||
$this->textField('geb'),
|
||||
$this->textField('lastname'),
|
||||
])
|
||||
])->create();
|
||||
|
||||
$this->patchJson(route('form.update', ['form' => $form]), $payload)->assertSessionDoesntHaveErrors()->assertOk();
|
||||
$this->assertEquals(['firstname', 'geb', 'lastname'], $form->fresh()->meta['active_columns']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class FormtemplateFieldRequest extends RequestFactory
|
|||
'columns' => ['mobile' => 2, 'tablet' => 4, 'desktop' => 6],
|
||||
'nami_type' => null,
|
||||
'for_members' => true,
|
||||
'hint' => '',
|
||||
'hint' => null,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ class FormtemplateIndexActionTest extends TestCase
|
|||
'name' => '',
|
||||
'type' => 'TextField',
|
||||
'columns' => ['mobile' => 2, 'tablet' => 4, 'desktop' => 6],
|
||||
'value' => '',
|
||||
'value' => null,
|
||||
'nami_type' => null,
|
||||
'for_members' => true,
|
||||
'special_type' => null,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Form;
|
||||
|
||||
use App\Form\Models\Form;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class IsDirtyActionTest extends FormTestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function testItChecksIfFormIsDirty(): void
|
||||
{
|
||||
$this->login()->loginNami();
|
||||
$form = Form::factory()->fields([
|
||||
$this->textField(),
|
||||
])->create();
|
||||
|
||||
$this->postJson(route('form.is-dirty', ['form' => $form]), ['config' => $form->config->toArray()])->assertJsonPath('result', false);
|
||||
|
||||
$modifiedConfig = $form->config->toArray();
|
||||
data_set($modifiedConfig, 'sections.0.name', 'mod');
|
||||
$this->postJson(route('form.is-dirty', ['form' => $form]), ['config' => $modifiedConfig])->assertJsonPath('result', true);
|
||||
}
|
||||
}
|
|
@ -20,21 +20,28 @@ class EditorRequestFactory extends RequestFactory
|
|||
];
|
||||
}
|
||||
|
||||
public function text(int $id, string $text): self
|
||||
public function text(int $id, string $text, array $conditions = ['mode' => 'all', 'ifs' => []]): self
|
||||
{
|
||||
return $this->state($this->paragraphBlock($id, $text));
|
||||
return $this->state($this->paragraphBlock($id, $text, $conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function paragraphBlock(int $id, string $text): array
|
||||
public function paragraphBlock(int $id, string $text, array $conditions = ['mode' => 'all', 'ifs' => []]): array
|
||||
{
|
||||
return [
|
||||
'time' => 1,
|
||||
'version' => '1.0',
|
||||
'blocks' => [
|
||||
['id' => $id, 'type' => 'paragraph', 'data' => ['text' => $text]]
|
||||
[
|
||||
'id' => $id,
|
||||
'type' => 'paragraph',
|
||||
'data' => ['text' => $text],
|
||||
'tunes' => [
|
||||
'condition' => $conditions
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue