Add prevention settings
This commit is contained in:
parent
ca9bf70c9a
commit
1d09afbeb3
|
@ -2,12 +2,16 @@
|
|||
|
||||
namespace App\Lib\Editor;
|
||||
|
||||
use Livewire\Wireable;
|
||||
use Spatie\LaravelData\Concerns\WireableData;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
/** @todo replace blocks with actual block data classes */
|
||||
class EditorData extends Data implements Editorable
|
||||
class EditorData extends Data implements Editorable, Wireable
|
||||
{
|
||||
|
||||
use WireableData;
|
||||
|
||||
/** @param array<int, mixed> $blocks */
|
||||
public function __construct(
|
||||
public string $version,
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Prevention\Actions;
|
||||
|
||||
use App\Prevention\PreventionSettings;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class SettingApiAction
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => app(PreventionSettings::class)->toArray(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Prevention\Actions;
|
||||
|
||||
use App\Lib\Editor\EditorData;
|
||||
use App\Lib\Events\Succeeded;
|
||||
use App\Prevention\PreventionSettings;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class SettingStoreAction
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'formmail' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function handle(ActionRequest $request): void
|
||||
{
|
||||
$settings = app(PreventionSettings::class);
|
||||
$settings->formmail = EditorData::from($request->formmail);
|
||||
$settings->save();
|
||||
|
||||
Succeeded::message('Einstellungen gespeichert.')->dispatch();
|
||||
}
|
||||
}
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
namespace App\Setting;
|
||||
|
||||
use App\Fileshare\FileshareSettings;
|
||||
use App\Form\FormSettings;
|
||||
use Modules\Module\ModuleSettings;
|
||||
use App\Prevention\PreventionSettings;
|
||||
use App\Setting\Data\SettingSynthesizer;
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
@ -35,8 +32,6 @@ class SettingServiceProvider extends ServiceProvider
|
|||
app(SettingFactory::class)->register(ModuleSettings::class);
|
||||
app(SettingFactory::class)->register(InvoiceSettings::class);
|
||||
app(SettingFactory::class)->register(NamiSettings::class);
|
||||
app(SettingFactory::class)->register(FileshareSettings::class);
|
||||
app(SettingFactory::class)->register(PreventionSettings::class);
|
||||
|
||||
Livewire::propertySynthesizer(SettingSynthesizer::class);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Form;
|
||||
|
||||
use App\View\Traits\HasFormDimensions;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Editor extends Component
|
||||
{
|
||||
|
||||
use HasFormDimensions;
|
||||
|
||||
public string $id;
|
||||
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $size = 'default',
|
||||
public ?string $hint = null,
|
||||
public bool $required = false,
|
||||
public string $label = '',
|
||||
public array $conditions = [],
|
||||
) {
|
||||
$this->id = str()->uuid()->toString();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<div class="flex flex-col group {{$heightClass}}" x-data="">
|
||||
@if ($label)
|
||||
<x-form::label :required="$required">{{$label}}</x-form::label>
|
||||
@endif
|
||||
|
||||
<div class="relative w-full h-full">
|
||||
<div class="
|
||||
w-full border-gray-600 border-solid text-gray-300 bg-gray-700 leading-none rounded-lg
|
||||
group-[.size-default]:border-2 group-[.size-sm]:border
|
||||
group-[.size-default]:text-sm group-[.size-sm]:text-xs
|
||||
group-[.size-default]:p-2 group-[.size-sm]:p-1
|
||||
" @updated="$wire.{{$attributes->wire('model')->value}} = $event.detail" x-bind="editor"x-data="{
|
||||
conditions: @js($conditions),
|
||||
value: $wire.{{$attributes->wire('model')->value}}
|
||||
}" id="{{$id}}" {{$attributes}}></div>
|
||||
<x-ui::errors :for="$name" />
|
||||
@if($hint)
|
||||
<x-form::hint>{{$hint}}</x-form::hint>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace Modules\Form\Components;
|
|||
use App\Form\FormSettings;
|
||||
use Livewire\Component;
|
||||
|
||||
class SettingView extends Component
|
||||
class FormSettingView extends Component
|
||||
{
|
||||
|
||||
public string $clearCacheUrl;
|
|
@ -13,7 +13,7 @@ uses(DatabaseTransactions::class);
|
|||
it('it renders page', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
test()->get('/setting/form')->assertSeeLivewire(SettingView::class);
|
||||
test()->get('/setting/form')->assertSeeLivewire(FormSettingView::class);
|
||||
});
|
||||
|
||||
it('it displays active modules', function () {
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Form\Components;
|
||||
|
||||
use App\Lib\Editor\EditorData;
|
||||
use App\Prevention\PreventionSettings;
|
||||
use Livewire\Component;
|
||||
|
||||
class PreventionSettingView extends Component
|
||||
{
|
||||
|
||||
public EditorData $formmail;
|
||||
public $settingClass = PreventionSettings::class;
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'formmail' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->formmail = app(PreventionSettings::class)->formmail;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
app(PreventionSettings::class)->fill(['formmail' => $this->formmail])->save();
|
||||
$this->dispatch('success', 'Einstellungen gespeichert.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return <<<'HTML'
|
||||
<x-page::setting-layout :active="$settingClass">
|
||||
<x-slot:right>
|
||||
<x-form::save-button form="preventionsettingform"></x-form::save-button>
|
||||
</x-slot:right>
|
||||
<form id="preventionsettingform" class="grow p-6 grid grid-cols-1 gap-3 items-start content-start"
|
||||
wire:submit.prevent="save">
|
||||
<x-ui::setting-intro>
|
||||
Hier kannst du Einstellungen zu Prävention setzen.
|
||||
</x-ui::setting-intro>
|
||||
<x-form::editor hint="lala" name="frommail" wire:model="formmail" label="E-Mail für Veranstaltungs-TN"></x-form::editor>
|
||||
</form>
|
||||
</x-page::setting-layout>
|
||||
HTML;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Form\Components;
|
||||
|
||||
use App\Prevention\PreventionSettings;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Livewire\Livewire;
|
||||
use Tests\RequestFactories\EditorRequestFactory;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
it('it renders page', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
test()->get('/setting/prevention')->assertSeeLivewire(PreventionSettingView::class);
|
||||
});
|
||||
|
||||
it('displays settings', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
$text = EditorRequestFactory::new()->text(50, 'lorem ipsum')->toData();
|
||||
app(PreventionSettings::class)->fill(['formmail' => $text])->save();
|
||||
|
||||
Livewire::test(PreventionSettingView::class)
|
||||
->assertSet('formmail.blocks.0.data.text', 'lorem ipsum')
|
||||
->assertSeeHtml('data-active');
|
||||
});
|
||||
|
||||
it('saves settings', function () {
|
||||
test()->withoutExceptionHandling()->login()->loginNami();
|
||||
|
||||
$text = EditorRequestFactory::new()->text(50, 'new lorem')->create();
|
||||
Livewire::test(PreventionSettingView::class)
|
||||
->set('formmail', $text)
|
||||
->call('save')
|
||||
->assertDispatched('success', 'Einstellungen gespeichert.');
|
||||
|
||||
$this->assertTrue(app(PreventionSettings::class)->formmail->hasAll(['new lorem']));
|
||||
});
|
|
@ -4,9 +4,11 @@ namespace Modules\Form;
|
|||
|
||||
use Illuminate\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Form\Components\SettingView;
|
||||
use Modules\Form\Components\FormSettingView;
|
||||
use App\Setting\SettingFactory;
|
||||
use App\Form\FormSettings;
|
||||
use App\Prevention\PreventionSettings;
|
||||
use Modules\Form\Components\PreventionSettingView;
|
||||
|
||||
class FormServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -27,9 +29,11 @@ class FormServiceProvider extends ServiceProvider
|
|||
public function boot()
|
||||
{
|
||||
app(SettingFactory::class)->register(FormSettings::class);
|
||||
app(SettingFactory::class)->register(PreventionSettings::class);
|
||||
|
||||
app(Router::class)->middleware(['web', 'auth:web'])->group(function ($router) {
|
||||
$router->get('/setting/form', SettingView::class)->name('setting.form');
|
||||
$router->get('/setting/form', FormSettingView::class)->name('setting.form');
|
||||
$router->get('/setting/prevention', PreventionSettingView::class)->name('setting.prevention');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'tippy.js/dist/tippy.css';
|
|||
import 'tippy.js/animations/shift-toward.css';
|
||||
import '../css/tooltip.css';
|
||||
import {error, success} from './toastify.js';
|
||||
import editor from './editor.js';
|
||||
|
||||
Alpine.plugin(
|
||||
Tooltip.defaultProps({
|
||||
|
@ -16,4 +17,8 @@ Alpine.plugin(
|
|||
|
||||
window.addEventListener('success', (event) => success(event.detail[0]));
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.bind('editor', () => editor);
|
||||
});
|
||||
|
||||
Livewire.start();
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
import {debounce} from 'lodash';
|
||||
import EditorJS from '@editorjs/editorjs';
|
||||
import Header from '@editorjs/header';
|
||||
import Paragraph from '@editorjs/paragraph';
|
||||
import NestedList from '@editorjs/nested-list';
|
||||
import Alert from 'editorjs-alert';
|
||||
|
||||
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');
|
||||
|
||||
var tooltip = document.createElement('div');
|
||||
tooltip.setAttribute('data-tooltip', '');
|
||||
|
||||
var content = document.createElement('div');
|
||||
content.setAttribute('data-content', '');
|
||||
|
||||
content.appendChild(blockContent);
|
||||
|
||||
this.wrapper.appendChild(tooltip);
|
||||
this.wrapper.appendChild(content);
|
||||
|
||||
this.styleWrapper();
|
||||
|
||||
return this.wrapper;
|
||||
}
|
||||
|
||||
hasData() {
|
||||
return this.data.ifs.length > 0;
|
||||
}
|
||||
|
||||
styleWrapper() {
|
||||
if (this.hasData()) {
|
||||
this.wrapper.querySelector('[data-content]').className = 'p-1 border border-blue-100 rounded';
|
||||
this.wrapper.querySelector('[data-tooltip]').className =
|
||||
'mt-1 inline-block tracking-wider font-semibold ml-2 mr-2 px-2 py-1 items-center text-xs leading-none bg-blue-100 text-blue-900 rounded-t-lg';
|
||||
this.wrapper.querySelector('[data-tooltip]').innerHTML = this.descriptionName();
|
||||
} else {
|
||||
this.wrapper.querySelector('[data-content]').className = '';
|
||||
this.wrapper.querySelector('[data-tooltip]').className = '';
|
||||
this.wrapper.querySelector('[data-tooltip]').innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
descriptionName() {
|
||||
return (
|
||||
'Bedingung ' +
|
||||
this.data.ifs
|
||||
.map((i) => {
|
||||
var parts = [i.field];
|
||||
|
||||
if (i.comparator === 'isEqual' || i.comparator === 'isIn') {
|
||||
parts.push('=');
|
||||
}
|
||||
|
||||
if (i.comparator === 'isNotEqual' || i.comparator === 'isNotIn') {
|
||||
parts.push('≠');
|
||||
}
|
||||
|
||||
if (typeof i.value === 'string') {
|
||||
parts.push(i.value);
|
||||
}
|
||||
|
||||
if (Array.isArray(i.value)) {
|
||||
parts.push(i.value.join(', '));
|
||||
}
|
||||
|
||||
if (typeof i.value === 'boolean') {
|
||||
parts.push(i.value ? 'An' : 'Aus');
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
})
|
||||
.join(', ')
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
'editor': null,
|
||||
'value': null,
|
||||
|
||||
'x-init': async function () {
|
||||
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');
|
||||
// }
|
||||
|
||||
this.editor = new EditorJS({
|
||||
placeholder: '',
|
||||
holder: this.$el.getAttribute('id'),
|
||||
minHeight: 0,
|
||||
defaultBlock: 'paragraph',
|
||||
data: this.value,
|
||||
tunes: tunes,
|
||||
tools: tools,
|
||||
onChange: debounce(async (api, event) => {
|
||||
const data = await this.editor.save();
|
||||
this.$dispatch('updated', data);
|
||||
}, 200),
|
||||
onPopup: () => {
|
||||
console.log('opened');
|
||||
},
|
||||
});
|
||||
await this.editor.isReady;
|
||||
},
|
||||
};
|
||||
|
||||
// const props = defineProps({
|
||||
// required: {
|
||||
// type: Boolean,
|
||||
// default: false,
|
||||
// },
|
||||
// size: {
|
||||
// type: String,
|
||||
// default: () => 'base',
|
||||
// },
|
||||
// rows: {
|
||||
// type: Number,
|
||||
// default: () => 4,
|
||||
// },
|
||||
// id: {
|
||||
// type: String,
|
||||
// required: true,
|
||||
// },
|
||||
// conditions: {
|
||||
// required: false,
|
||||
// type: Boolean,
|
||||
// default: () => false,
|
||||
// },
|
||||
// hint: {
|
||||
// type: String,
|
||||
// default: () => '',
|
||||
// },
|
||||
// modelValue: {
|
||||
// default: undefined,
|
||||
// },
|
||||
// label: {
|
||||
// type: String,
|
||||
// default: () => '',
|
||||
// },
|
||||
// });
|
||||
|
||||
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;
|
||||
});
|
||||
});
|
||||
}
|
|
@ -3,13 +3,9 @@
|
|||
use App\Contribution\Actions\GenerateApiAction as ContributionGenerateApiAction;
|
||||
use App\Form\Actions\FormApiListAction;
|
||||
use App\Form\Actions\RegisterAction;
|
||||
use App\Prevention\Actions\SettingStoreAction as PreventionStoreAction;
|
||||
use App\Group\Actions\GroupApiIndexAction;
|
||||
use App\Prevention\Actions\SettingApiAction;
|
||||
|
||||
Route::post('/contribution-generate', ContributionGenerateApiAction::class)->name('api.contribution.generate')->middleware('client:contribution-generate');
|
||||
Route::post('/form/{form}/register', RegisterAction::class)->name('form.register');
|
||||
Route::get('/group/{group?}', GroupApiIndexAction::class)->name('api.group');
|
||||
Route::get('/form', FormApiListAction::class)->name('api.form.index');
|
||||
Route::get('/prevention', SettingApiAction::class)->name('api.prevention.index');
|
||||
Route::post('/prevention', PreventionStoreAction::class)->name('api.prevention.store');
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Prevention;
|
||||
|
||||
use App\Prevention\PreventionSettings;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\RequestFactories\EditorRequestFactory;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SettingTest extends TestCase
|
||||
{
|
||||
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function testItOpensSettingsPage(): void
|
||||
{
|
||||
$this->login()->loginNami();
|
||||
|
||||
$this->get('/setting/prevention')->assertComponent('setting/Prevention')->assertOk();
|
||||
}
|
||||
|
||||
public function testItReceivesSettings(): void
|
||||
{
|
||||
$this->login()->loginNami();
|
||||
|
||||
$text = EditorRequestFactory::new()->text(50, 'lorem ipsum')->toData();
|
||||
app(PreventionSettings::class)->fill(['formmail' => $text])->save();
|
||||
|
||||
$this->get('/api/prevention')
|
||||
->assertJsonPath('data.formmail.blocks.0.data.text', 'lorem ipsum');
|
||||
}
|
||||
|
||||
public function testItStoresSettings(): void
|
||||
{
|
||||
$this->login()->loginNami();
|
||||
|
||||
$this->post('/api/prevention', ['formmail' => EditorRequestFactory::new()->text(50, 'new lorem')->create()])->assertOk();
|
||||
$this->assertTrue(app(PreventionSettings::class)->formmail->hasAll(['new lorem']));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue