FormBuilderTS

This commit is contained in:
Thomas Peterson 2025-07-15 13:18:51 +02:00
parent dd2af7f810
commit 5ac5e328e2
36 changed files with 562 additions and 195 deletions

View File

@ -0,0 +1,30 @@
<?php
namespace Plugins\System\PSC\XmlCalc\Api;
use PSC\Shop\ContactBundle\Repository\ContactRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Tests\RefreshDatabaseTrait;
class DesignTest extends WebTestCase
{
use RefreshDatabaseTrait;
public function testDesignApi(): void
{
$client = static::createClient();
$client->jsonRequest(
'POST',
'/api/plugin/system/psc/xmlcalc/product/design',
[
'product' => '01938686-0e4d-7da9-bae3-b2e1b1681f9f',
'jsonProduct' => '[{"uuid":"df2df718-b28e-482d-bf0c-67d246f05d32","name":"Test Artikel","options":[{"id":"auflage","name":"Auflage","default":"100","dependencys":[],"placeHolder":"Placeholder","required":false,"type":2},{"id":"seiten_umschlag","name":"Seiten Umschlag","default":"2","dependencys":[],"placeHolder":"Placeholder","required":false,"type":2},{"id":"seiten_anzahl_inhalt","name":"Seiten Anzahl Inhalt","default":"10","dependencys":[{"relation":"auflage","formula":"","borders":[{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"$Vauflage$V*0.12","price":0,"value":"1-10","dependencys":[{"relation":"seiten_umschlag","formula":"","borders":[{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"$Vseiten_umschlag$V*0.24","price":0,"value":"1-2","dependencys":[]},{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"$Vseiten_umschlag$V*0.23","price":0,"value":"3-","dependencys":[]}]}]},{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"$Vauflage$V*0.11","price":0,"value":"11-","dependencys":[{"relation":"seiten_umschlag","formula":"","borders":[{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"$Vseiten_umschlag$V*0.21","price":0,"value":"1-2","dependencys":[]},{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"$Vseiten_umschlag$V*0.20","price":0,"value":"3-","dependencys":[]}]}]}]}],"placeHolder":"Placeholder","required":true,"type":2},{"id":"farbigkeit","name":"Farbigkeit","default":"10","dependencys":[],"type":3,"options":[{"id":"10","name":"1\/0 farbig","dependencys":[{"relation":"auflage","formula":"","borders":[{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"","price":0,"value":"1-101","dependencys":[]}]}]},{"id":"11","name":"1\/1 farbig","dependencys":[]},{"id":"20","name":"2\/0 farbig","dependencys":[]},{"id":"21","name":"2\/1 farbig","dependencys":[]},{"id":"22","name":"2\/2 farbig","dependencys":[{"relation":"auflage","formula":"","borders":[{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"","price":0,"value":"11-50","dependencys":[]}]}]}],"mode":"normal"},{"id":"calc","name":"calc","dependencys":[{"relation":"auflage","formula":"","borders":[{"calcValue":"","calcValue1":"","calcValue2":"","calcValue3":"","calcValue4":"","calcValue5":"","calcValue6":"","calcValue7":"","calcValue8":"","calcValue9":"","calcValue10":"","flatRate":"","formula":"$Vauflage$V*$Vseiten_anzahl_inhalt$V","price":0,"value":"1-","dependencys":[]}]}],"type":1}]}]',
],
[],
);
$this->assertResponseIsSuccessful();
self::assertJson($client->getResponse()->getContent());
}
}

View File

@ -21,6 +21,8 @@
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-codemirror": "^6.1.1", "vue-codemirror": "^6.1.1",
"vue-draggable-plus": "^0.6.0", "vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.1.9",
"xml-beautify": "^1.2.3",
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.1", "@types/node": "^24.0.1",
@ -176,6 +178,12 @@
"@internationalized/number": ["@internationalized/number@3.6.3", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw=="], "@internationalized/number": ["@internationalized/number@3.6.3", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw=="],
"@intlify/core-base": ["@intlify/core-base@11.1.9", "", { "dependencies": { "@intlify/message-compiler": "11.1.9", "@intlify/shared": "11.1.9" } }, "sha512-Lrdi4wp3XnGhWmB/mMD/XtfGUw1Jt+PGpZI/M63X1ZqhTDjNHRVCs/i8vv8U1cwaj1A9fb0bkCQHLSL0SK+pIQ=="],
"@intlify/message-compiler": ["@intlify/message-compiler@11.1.9", "", { "dependencies": { "@intlify/shared": "11.1.9", "source-map-js": "^1.0.2" } }, "sha512-84SNs3Ikjg0rD1bOuchzb3iK1vR2/8nxrkyccIl5DjFTeMzE/Fxv6X+A7RN5ZXjEWelc1p5D4kHA6HEOhlKL5Q=="],
"@intlify/shared": ["@intlify/shared@11.1.9", "", {}, "sha512-H/83xgU1l8ox+qG305p6ucmoy93qyjIPnvxGWRA7YdOoHe1tIiW9IlEu4lTdsOR7cfP1ecrwyflQSqXdXBacXA=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
@ -610,12 +618,16 @@
"vue-draggable-plus": ["vue-draggable-plus@0.6.0", "", { "dependencies": { "@types/sortablejs": "^1.15.8" } }, "sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw=="], "vue-draggable-plus": ["vue-draggable-plus@0.6.0", "", { "dependencies": { "@types/sortablejs": "^1.15.8" } }, "sha512-G5TSfHrt9tX9EjdG49InoFJbt2NYk0h3kgjgKxkFWr3ulIUays0oFObr5KZ8qzD4+QnhtALiRwIqY6qul4egqw=="],
"vue-i18n": ["vue-i18n@11.1.9", "", { "dependencies": { "@intlify/core-base": "11.1.9", "@intlify/shared": "11.1.9", "@vue/devtools-api": "^6.5.0" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-N9ZTsXdRmX38AwS9F6Rh93RtPkvZTkSy/zNv63FTIwZCUbLwwrpqlKz9YQuzFLdlvRdZTnWAUE5jMxr8exdl7g=="],
"vue-tsc": ["vue-tsc@2.2.10", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.10" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ=="], "vue-tsc": ["vue-tsc@2.2.10", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.10" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ=="],
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"xml-beautify": ["xml-beautify@1.2.3", "", {}, "sha512-VsYpkqoVawIP84pi00XukPsgQHqOhgrpwTHlXqqRMAgYZ1u+Yw3KHIUhO1Igf19d5CQ5h6ExJT1hFCJRLmzADg=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="], "yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="],
@ -642,6 +654,8 @@
"reka-ui/@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="], "reka-ui/@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="],
"vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
"reka-ui/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@12.8.2", "", {}, "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A=="], "reka-ui/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@12.8.2", "", {}, "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A=="],
} }
} }

View File

@ -26,7 +26,9 @@
"uuid": "^11.1.0", "uuid": "^11.1.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-codemirror": "^6.1.1", "vue-codemirror": "^6.1.1",
"vue-draggable-plus": "^0.6.0" "vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.1.9",
"xml-beautify": "^1.2.3"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.1", "@types/node": "^24.0.1",

View File

@ -8,7 +8,8 @@ onMounted(() => {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const uuid = params.get('uuid'); const uuid = params.get('uuid');
if (uuid) { if (uuid) {
store.loadFromApi(uuid); store.loadConfigFromProductApi(uuid);
store.loadFormulaAnalyserDataFromApi(uuid);
} }
}) })
</script> </script>

View File

@ -7,8 +7,7 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs'
import { Library } from '../components/app/library' import { Library } from '../components/app/library'
import { Debug } from '../components/app/debug' import { ElementProperties, SpecialElementProperties } from '../components/app/elementproperties'
import { ElementProperties } from '../components/app/elementproperties'
import { ElementDependency } from '../components/app/elementdependency' import { ElementDependency } from '../components/app/elementdependency'
import { Main } from '../components/app/main' import { Main } from '../components/app/main'
import FormulaVisualizer from './app/FormulaVisualizer.vue' import FormulaVisualizer from './app/FormulaVisualizer.vue'
@ -26,7 +25,6 @@ import XmlView from './app/XmlView.vue'
<ResizablePanel id="" :default-size="15"> <ResizablePanel id="" :default-size="15">
<div class="flex flex-col gap-2 h-full"> <div class="flex flex-col gap-2 h-full">
<Library /> <Library />
<Debug />
</div> </div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle id="" with-handle /> <ResizableHandle id="" with-handle />
@ -35,16 +33,16 @@ import XmlView from './app/XmlView.vue'
<div class="flex justify-center"> <div class="flex justify-center">
<TabsList> <TabsList>
<TabsTrigger value="designer"> <TabsTrigger value="designer">
Designer {{ $t('designer') }}
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="preview"> <TabsTrigger value="preview">
Kalkulations Analyse {{ $t('preview') }}
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="xml"> <TabsTrigger value="xml">
XML Ansicht {{ $t('xml_view') }}
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="json"> <TabsTrigger value="json">
JSON Ansicht {{ $t('json_view') }}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
</div> </div>
@ -66,6 +64,7 @@ import XmlView from './app/XmlView.vue'
</ResizablePanel> </ResizablePanel>
</ResizablePanelGroup> </ResizablePanelGroup>
<ElementProperties /> <ElementProperties />
<SpecialElementProperties />
<ElementDependency /> <ElementDependency />
</div> </div>
</template> </template>

View File

@ -1,22 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, provide } from 'vue'; import { ref, onMounted, provide, computed } from 'vue';
import NodeRenderer from './NodeRenderer.vue'; import NodeRenderer from './NodeRenderer.vue';
import { loadPriceFromApi } from '../../lib/api';
import { useElementStore } from '../../stores/Items'; import { useElementStore } from '../../stores/Items';
// Debounce function to limit the rate at which a function gets called.
function debounce<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => void {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
func(...args);
}, delay);
};
}
interface Node { interface Node {
name: string; name: string;
unParsed?: string; unParsed?: string;
@ -25,50 +11,12 @@ interface Node {
parts: Node[]; parts: Node[];
} }
const parsedData = ref<Node[] | null>(null);
const expandedNodes = ref(new Set<string>()); const expandedNodes = ref(new Set<string>());
const error = ref('');
const isLoading = ref(false);
const store = useElementStore(); const store = useElementStore();
const loadFormulaData = async () => { const parsedData = computed(() => store.getFormulaData);
isLoading.value = true; const error = computed(() => store.getFormulaError);
error.value = ''; const isLoading = computed(() => store.isFormulaLoading);
parsedData.value = null;
const urlParams = new URLSearchParams(window.location.search);
const uuid = urlParams.get('uuid');
if (!uuid) {
error.value = 'Keine UUID in der URL gefunden.';
isLoading.value = false;
return;
}
try {
const response = await loadPriceFromApi(uuid);
if (response && response.debug && response.debug.graphJson) {
const graphData = JSON.parse(response.debug.graphJson);
parsedData.value = graphData;
} else {
throw new Error('Ungültiges oder leeres Antwortformat von der API.');
}
} catch (e: any) {
error.value = `Fehler beim Laden der Formeldaten: ${e.message}`;
console.error(e);
} finally {
isLoading.value = false;
}
};
const debouncedLoadFormulaData = debounce(loadFormulaData, 500);
onMounted(() => {
loadFormulaData();
store.$subscribe(() => {
debouncedLoadFormulaData();
});
});
const toggleNode = (nodeId: string) => { const toggleNode = (nodeId: string) => {
const newExpanded = new Set(expandedNodes.value); const newExpanded = new Set(expandedNodes.value);

View File

@ -10,11 +10,19 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { computed } from 'vue';
import { Codemirror } from 'vue-codemirror'; import { Codemirror } from 'vue-codemirror';
import { xml } from '@codemirror/lang-xml'; import { xml } from '@codemirror/lang-xml';
import { useElementStore } from '../../stores/Items';
import XmlBeautify from 'xml-beautify'
const xmlString = ref('<root>\n <!-- XML Data Source Not Yet Implemented -->\n</root>'); const store = useElementStore();
const xmlString = computed(() => new XmlBeautify().beautify(store.xml,
{
indent: " ", //indent pattern like white spaces
useSelfClosingElement: true //true:use self-closing element when empty element.
}));
const extensions = [xml()]; const extensions = [xml()];

View File

@ -1,24 +0,0 @@
<script lang="ts" setup>
import { useElementStore } from '../../../stores/Items';
import { Textarea } from '../../../components/ui/textarea'
import { Button } from '../../../components/ui/button'
const store = useElementStore()
function loadJson() {
store.loadJSON()
}
function restoreJson() {
store.parseJSON()
}
</script>
<template>
<div class="p-1">
<Textarea v-model="store.json" class="h-50"/>
<div class="flex flex-row mt-2 gap-1">
<Button class="grow" v-on:click="loadJson()">Load Json</Button>
<Button class="grow" v-on:click="restoreJson()">Restore Json</Button>
</div>
</div>
</template>

View File

@ -1 +0,0 @@
export { default as Debug } from './Debug.vue'

View File

@ -0,0 +1,20 @@
<script lang="ts" setup>
import { useElementStore } from '../../../stores/Items'
import SelectSpecialProperties from '../properties/SelectSpecial.vue'
import { ref, watch } from 'vue'
let emit = defineEmits(['update:modelValue']);
const store = useElementStore()
</script>
<template>
<div class="">
<SelectSpecialProperties
v-model="store.getActiveItem as SelectElement"
>
</SelectSpecialProperties>
</div>
</template>

View File

@ -1 +1,2 @@
export { default as ElementProperties } from './ElementProperties.vue' export { default as ElementProperties } from './ElementProperties.vue'
export { default as SpecialElementProperties } from './SpecialProperties.vue'

View File

@ -19,7 +19,7 @@ const theModel = computed<InputElement>({
<template> <template>
<div class="flex gap-2 flex-row items-center"> <div class="flex gap-2 flex-row items-center">
<label class="w-60 flex-inital">{{theModel.name}} {{theModel.default}}</label> <label class="w-60 flex-inital">{{theModel.name}}</label>
<Input v-model:placeholder="theModel.placeHolder" :modelValue="theModel.default" v-model:name="theModel.name" v-model:id="theModel.id" v-model:required="theModel.required"/> <Input v-model:placeholder="theModel.placeHolder" :modelValue="theModel.default" v-model:name="theModel.name" v-model:id="theModel.id" v-model:required="theModel.required"/>
</div> </div>
</template> </template>

View File

@ -4,7 +4,9 @@ import { Image, SquareParking, SquareDot, SquareMenu, SquarePen, SquareChevronDo
import { Switch } from '../../../components/ui/switch' import { Switch } from '../../../components/ui/switch'
import { Label } from '../../../components/ui/label' import { Label } from '../../../components/ui/label'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
const { t, locale } = useI18n()
const store = useElementStore() const store = useElementStore()
let previewMode = ref(false) let previewMode = ref(false)
@ -30,41 +32,47 @@ watch(previewMode, (newPreviewMode) => {
<template> <template>
<div class="flex flex-col p-3 gap-3 overflow-auto"> <div class="flex flex-col p-3 gap-3 overflow-auto">
<div class="flex items-center space-x-2">
<select v-model="locale">
<option value="de">DE</option>
<option value="en">EN</option>
</select>
</div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<Switch id="preview-mode" v-model="previewMode" /> <Switch id="preview-mode" v-model="previewMode" />
<Label for="preview-mode">Preview Mode</Label> <Label for="preview-mode">{{ $t('preview_mode') }}</Label>
</div> </div>
<div id="headline" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '6')" @dragenter.prevent @dragover.prevent> <div id="headline" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '6')" @dragenter.prevent @dragover.prevent>
<SquareDot /> <SquareDot />
<span>Headline</span> <span>{{ $t('headline') }}</span>
</div> </div>
<div id="text" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '4')" @dragenter.prevent @dragover.prevent> <div id="text" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '4')" @dragenter.prevent @dragover.prevent>
<SquareParking /> <SquareParking />
<span>Text</span> <span>{{ $t('text') }}</span>
</div> </div>
<div id="media" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '9')" @dragenter.prevent @dragover.prevent> <div id="media" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '9')" @dragenter.prevent @dragover.prevent>
<Image /> <Image />
<span>Media</span> <span>{{ $t('media') }}</span>
</div> </div>
<div id="textarea" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '5')" @dragenter.prevent @dragover.prevent> <div id="textarea" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '5')" @dragenter.prevent @dragover.prevent>
<SquareMenu /> <SquareMenu />
<span>Textarea</span> <span>{{ $t('textarea') }}</span>
</div> </div>
<div id="input" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '2')" @dragenter.prevent @dragover.prevent> <div id="input" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '2')" @dragenter.prevent @dragover.prevent>
<SquarePen /> <SquarePen />
<span>Input</span> <span>{{ $t('input') }}</span>
</div> </div>
<div id="select" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '3')" @dragenter.prevent @dragover.prevent> <div id="select" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '3')" @dragenter.prevent @dragover.prevent>
<SquareChevronDown /> <SquareChevronDown />
<span>Select</span> <span>{{ $t('select') }}</span>
</div> </div>
<div id="hidden" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '1')" @dragenter.prevent @dragover.prevent> <div id="hidden" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '1')" @dragenter.prevent @dragover.prevent>
<SquareDashed /> <SquareDashed />
<span>Hidden</span> <span>{{ $t('hidden') }}</span>
</div> </div>
<div id="row" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '7')" @dragenter.prevent @dragover.prevent> <div id="row" class="border-1 p-2 w-full flex flex-row gap-2" draggable="true" @dragstart="startDrag($event, '7')" @dragenter.prevent @dragover.prevent>
<SquareDashed /> <SquareDashed />
<span>Row</span> <span>{{ $t('row') }}</span>
</div> </div>
</div> </div>
</template> </template>

View File

@ -25,13 +25,13 @@ const theModel = computed({
</script> </script>
<template> <template>
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel!.id" /> <Input v-model="theModel!.id" />
<label>Default</label> <label>{{ $t('default') }}</label>
<Input v-model="theModel!.default"/> <Input v-model="theModel!.default"/>
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel!.name"/> <Input v-model="theModel!.name"/>
<label>Variant</label> <label>{{ $t('variant') }}</label>
<Select v-model="theModel!.variant"> <Select v-model="theModel!.variant">
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@ -39,22 +39,22 @@ const theModel = computed({
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem value="1"> <SelectItem value="1">
Headline 1 {{ $t('headline1') }}
</SelectItem> </SelectItem>
<SelectItem value="2"> <SelectItem value="2">
Headline 2 {{ $t('headline2') }}
</SelectItem> </SelectItem>
<SelectItem value="3"> <SelectItem value="3">
Headline 3 {{ $t('headline3') }}
</SelectItem> </SelectItem>
<SelectItem value="4"> <SelectItem value="4">
Headline 4 {{ $t('headline4') }}
</SelectItem> </SelectItem>
<SelectItem value="5"> <SelectItem value="5">
Headline 5 {{ $t('headline5') }}
</SelectItem> </SelectItem>
<SelectItem value="6"> <SelectItem value="6">
Headline 6 {{ $t('headline6') }}
</SelectItem> </SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>

View File

@ -17,10 +17,10 @@ const theModel = computed({
</script> </script>
<template> <template>
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel!.id" /> <Input v-model="theModel!.id" />
<label>Default</label> <label>{{ $t('default') }}</label>
<Input v-model="theModel!.default"/> <Input v-model="theModel!.default"/>
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel!.name"/> <Input v-model="theModel!.name"/>
</template> </template>

View File

@ -18,18 +18,22 @@ const theModel = computed<InputElement>({
</script> </script>
<template> <template>
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel!.id" /> <Input v-model="theModel!.id" />
<label>Placeholder</label> <label>{{ $t('placeholder') }}</label>
<Input v-model="theModel!.placeHolder"/> <Input v-model="theModel!.placeHolder"/>
<label>Default</label> <label>{{ $t('default') }}</label>
<Input v-model="theModel!.default"/> <Input v-model="theModel!.default"/>
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel!.name"/> <Input v-model="theModel!.name"/>
<Checkbox v-model="theModel!.required"/> <Checkbox v-model="theModel!.required"/>
<label class="form-check-label" for="flexSwitchCheckDefault">Required</label> <label class="form-check-label" for="flexSwitchCheckDefault">{{ $t('required') }}</label>
<label>Min</label> <label>{{ $t('min') }}</label>
<Input v-model="theModel!.minValue"/> <Input v-model="theModel!.minValue"/>
<label>Max</label> <label>{{ $t('max') }}</label>
<Input v-model="theModel!.maxValue"/> <Input v-model="theModel!.maxValue"/>
<label>{{ $t('min_calc') }}</label>
<Input v-model="theModel!.minCalc"/>
<label>{{ $t('max_calc') }}</label>
<Input v-model="theModel!.maxCalc"/>
</template> </template>

View File

@ -20,10 +20,10 @@ const theModel = computed({
</script> </script>
<template> <template>
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel!.id" /> <Input v-model="theModel!.id" />
<label>Default</label> <label>{{ $t('default') }}</label>
<Input v-model="theModel!.default"/> <Input v-model="theModel!.default"/>
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel!.name"/> <Input v-model="theModel!.name"/>
</template> </template>

View File

@ -26,11 +26,11 @@ const theModel = computed<Option>({
<template> <template>
<div class="flex flex-row gap-1"> <div class="flex flex-row gap-1">
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel.id" /> <Input v-model="theModel.id" />
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel.name"/> <Input v-model="theModel.name"/>
<Button v-on:click="addDependency(theModel)">Add Dependency</Button> <Button v-on:click="addDependency(theModel)">{{ $t('add_dependency') }}</Button>
</div> </div>
<Dependency :dependencys="theModel.dependencys" /> <Dependency :dependencys="theModel.dependencys" />
</template> </template>

View File

@ -24,5 +24,5 @@ function addColumn(row: Row) {
</script> </script>
<template> <template>
<Button v-on:click="addColumn(theModel!)">add Column</Button> <Button v-on:click="addColumn(theModel!)">{{ $t('add_column') }}</Button>
</template> </template>

View File

@ -23,31 +23,29 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from '../../../components/ui/dialog' } from '../../../components/ui/dialog'
import { ref, watch } from 'vue'
const props = defineProps<{ const props = defineProps<{
modelValue: SelectElement modelValue: SelectElement
}>() }>()
let emit = defineEmits(['update:modelValue']); let emit = defineEmits(['update:modelValue'])
const theModel = computed<SelectElement>({ const theModel = computed<SelectElement>({
get: () => props.modelValue, get: () => props.modelValue,
set: (value) => emit('update:modelValue', value), set: (value) => emit('update:modelValue', value),
}); });
function addOption(obj: SelectElement) {
obj.addOption(new Option(String(obj.options.length + 1)));
}
</script> </script>
<template> <template>
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel!.id" /> <Input v-model="theModel!.id" />
<label>Default</label> <label>{{ $t('default') }}</label>
<Input v-model="theModel!.default"/> <Input v-model="theModel!.default"/>
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel!.name"/> <Input v-model="theModel!.name"/>
<label>Mode</label> <label>{{ $t('mode') }}</label>
<Select v-model="theModel!.mode"> <Select v-model="theModel!.mode">
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@ -55,42 +53,17 @@ function addOption(obj: SelectElement) {
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem value="normal"> <SelectItem value="normal">
Normal {{ $t('normal') }}
</SelectItem> </SelectItem>
<SelectItem value="paperdb"> <SelectItem value="paperdb">
PaperDB {{ $t('paperdb') }}
</SelectItem> </SelectItem>
<SelectItem value="colordb"> <SelectItem value="colordb">
ColorDB {{ $t('colordb') }}
</SelectItem> </SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
<label>Container</label> <label>{{ $t('container') }}</label>
<Input v-model="theModel!.container"/> <Input v-model="theModel!.container"/>
<Dialog >
<DialogTrigger>
<Button class="mt-2">Edit Options</Button>
</DialogTrigger>
<DialogContent class="min-w-full grid-rows-[auto_minmax(0,1fr)_auto] p-4 max-h-[90dvh]">
<DialogHeader>
<DialogTitle>Edit Options</DialogTitle>
<DialogDescription>
<Button v-on:click="addOption(theModel)">Add Option</Button>
</DialogDescription>
</DialogHeader>
<div class="w-full grid overflow-y-auto px-6">
<div class="d-flex flex-wrap p-2 relative" v-for="opt in theModel.options" :key="opt.uuid">
<OptionElement :option="opt"/>
</div>
</div>
<DialogFooter>
<DialogClose as-child>
<Button type="button" variant="secondary">
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</template> </template>

View File

@ -0,0 +1,87 @@
<script lang="ts" setup>
import { useElementStore } from '../../../stores/Items'
import SelectElement from '../../../model/SelectElement';
import Option from '../../../model/select/Option';
import { computed } from 'vue';
import { Input } from '../../../components/ui/input'
import { Button } from '../../../components/ui/button'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '../../../components/ui/select'
import OptionElement from './OptionElement.vue'
import {
Dialog,
DialogContent,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '../../../components/ui/dialog'
import { ref, watch } from 'vue'
const props = defineProps<{
modelValue: SelectElement
}>()
let emit = defineEmits(['update:modelValue'])
let openModal = ref(false)
const theModel = computed<SelectElement>({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value),
});
function addOption(obj: SelectElement) {
obj.addOption(new Option(String(obj.options.length + 1)));
}
const store = useElementStore()
store.$subscribe((mutation, state) => {
if(state.showOptions) {
openModal.value = true
}
}, true)
watch(openModal, (newOpenModal) => {
if(newOpenModal === false) {
store.setShowOptions(false)
}
})
</script>
<template>
<Dialog v-if="store.getActiveItem.type === 3" v-model:open="openModal">
<DialogTrigger>
<Button class="mt-2">{{ $t('edit_options') }}</Button>
</DialogTrigger>
<DialogContent class="min-w-full grid-rows-[auto_minmax(0,1fr)_auto] p-4 max-h-[90dvh]">
<DialogHeader>
<DialogTitle>{{ $t('edit_options') }}</DialogTitle>
<DialogDescription>
<Button v-on:click="addOption(theModel)">{{ $t('add_option') }}</Button>
</DialogDescription>
</DialogHeader>
<div class="w-full grid overflow-y-auto px-6">
<div class="d-flex flex-wrap p-2 relative" v-for="opt in theModel.options" :key="opt.uuid">
<OptionElement :option="opt"/>
</div>
</div>
<DialogFooter>
<DialogClose as-child>
<Button type="button" variant="secondary">
{{ $t('close') }}
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</template>

View File

@ -18,10 +18,10 @@ const theModel = computed<TextElement>({
</script> </script>
<template> <template>
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel!.id" /> <Input v-model="theModel!.id" />
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel!.name"/> <Input v-model="theModel!.name"/>
<label>Default</label> <label>{{ $t('default') }}</label>
<Textarea v-model="theModel!.default"/> <Textarea v-model="theModel!.default"/>
</template> </template>

View File

@ -19,10 +19,10 @@ const theModel = computed<TextareaElement>({
</script> </script>
<template> <template>
<label>ID</label> <label>{{ $t('id') }}</label>
<Input v-model="theModel.id" /> <Input v-model="theModel.id" />
<label>Name</label> <label>{{ $t('name') }}</label>
<Input v-model="theModel.name"/> <Input v-model="theModel.name"/>
<label>Default</label> <label>{{ $t('default') }}</label>
<Textarea v-model="theModel.default"/> <Textarea v-model="theModel.default"/>
</template> </template>

View File

@ -18,7 +18,7 @@ import Row from '../../../model/Row'
import SelectElement from '../../../model/SelectElement' import SelectElement from '../../../model/SelectElement'
import { useElementStore } from '../../../stores/Items' import { useElementStore } from '../../../stores/Items'
import Parser from '../../../lib/parser' import Parser from '../../../lib/parser'
import { Settings, Trash, CirclePlus, Waypoints } from 'lucide-vue-next'; import { Settings, Trash, CirclePlus, List, Option } from 'lucide-vue-next';
import { ref } from 'vue' import { ref } from 'vue'
interface Props { interface Props {
@ -86,12 +86,16 @@ const deleteItem = (item: BaseElement) => {
} }
const editElementProperties = (item: BaseElement) => { const editElementProperties = (item: BaseElement) => {
store.setShowProperties(true)
store.setActiveItem(item) store.setActiveItem(item)
store.setShowProperties(true)
}
const editOptions = (item: BaseElement) => {
store.setActiveItem(item)
store.setShowOptions(true)
} }
const editElementDependency = (item: BaseElement) => { const editElementDependency = (item: BaseElement) => {
store.setShowDependency(true)
store.setActiveItem(item) store.setActiveItem(item)
store.setShowDependency(true)
} }
</script> </script>
@ -106,7 +110,7 @@ const editElementDependency = (item: BaseElement) => {
<span class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 bg-white left-1/2 dark:text-white dark:bg-gray-900 pointer-events-none"><CirclePlus :class="{ 'text-orange-500': dragUuid == item.uuid }" class="transition duration-200 pointer-events-none" /></span> <span class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 bg-white left-1/2 dark:text-white dark:bg-gray-900 pointer-events-none"><CirclePlus :class="{ 'text-orange-500': dragUuid == item.uuid }" class="transition duration-200 pointer-events-none" /></span>
</div> </div>
</div> </div>
<div :class="{ 'border-white' : item.dependencys.length == 0, 'border-blue-500': item.dependencys.length > 0 }" @dragstart="startDrag($event, item.uuid)" draggable="true" class="element w-full flex flex-row border-l-2 hover:border-orange-500 pl-2 transition duration-500 min-h-5" v-bind:class="{ ' bg-slate-50': item.isFocused === true }"> <div :class="{ 'border-white' : !item.hasDependencys(), 'border-blue-500': item.hasDependencys() }" @dragstart="startDrag($event, item.uuid)" draggable="true" class="element w-full flex flex-row border-l-2 hover:border-orange-500 pl-2 transition duration-500 min-h-5" v-bind:class="{ ' bg-slate-50': item.isFocused === true }">
<div class="grow content-center items-center"> <div class="grow content-center items-center">
<InputElementForm <InputElementForm
v-if="item.type === 2" v-if="item.type === 2"
@ -142,10 +146,13 @@ const editElementDependency = (item: BaseElement) => {
/> />
</div> </div>
<div class="buttons absolute rounded-sm invisible right-0 bg-slate-100/70 flex flex-row gap-2" v-if="!isPreview"> <div class="buttons absolute rounded-sm invisible right-0 bg-slate-100/70 flex flex-row gap-2" v-if="!isPreview">
<div v-on:click="editElementDependency(item)" class="m-2 cursor-pointer"> <div v-on:click="editElementDependency(item)" :title="$t('dependencies')" class="m-2 cursor-pointer">
<Waypoints /> <Option />
</div> </div>
<div v-on:click="editElementProperties(item)" class="m-2 cursor-pointer"> <div v-if="item.type === 3" v-on:click="editOptions(item)" :title="$t('options')" class="m-2 cursor-pointer">
<List />
</div>
<div v-on:click="editElementProperties(item)" :title="$t('settings')" class="m-2 cursor-pointer">
<Settings /> <Settings />
</div> </div>
<div v-on:click="deleteItem(item)" class="text-red-500 m-2 cursor-pointer"> <div v-on:click="deleteItem(item)" class="text-red-500 m-2 cursor-pointer">

View File

@ -0,0 +1,44 @@
{
"designer": "Designer",
"preview": "Kalkulations Analyse",
"xml_view": "XML Ansicht",
"json_view": "JSON Ansicht",
"preview_mode": "Vorschau-Modus",
"headline": "Überschrift",
"text": "Text",
"media": "Medien",
"textarea": "Textbereich",
"input": "Eingabefeld",
"select": "Auswahl",
"hidden": "Versteckt",
"row": "Zeile",
"id": "ID",
"default": "Standard",
"name": "Name",
"variant": "Variante",
"headline1": "Überschrift 1",
"headline2": "Überschrift 2",
"headline3": "Überschrift 3",
"headline4": "Überschrift 4",
"headline5": "Überschrift 5",
"headline6": "Überschrift 6",
"placeholder": "Platzhalter",
"required": "Erforderlich",
"min": "Min",
"max": "Max",
"min_calc": "Min Calc",
"max_calc": "Max Calc",
"add_dependency": "Abhängigkeit hinzufügen",
"add_column": "Spalte hinzufügen",
"mode": "Modus",
"normal": "Normal",
"paperdb": "PapierDB",
"colordb": "FarbDB",
"container": "Container",
"edit_options": "Optionen bearbeiten",
"add_option": "Option hinzufügen",
"close": "Schließen",
"dependencies": "Abhängigkeiten",
"options": "Optionen",
"settings": "Einstellungen"
}

View File

@ -0,0 +1,44 @@
{
"designer": "Designer",
"preview": "Calculation Analysis",
"xml_view": "XML View",
"json_view": "JSON View",
"preview_mode": "Preview Mode",
"headline": "Headline",
"text": "Text",
"media": "Media",
"textarea": "Textarea",
"input": "Input",
"select": "Select",
"hidden": "Hidden",
"row": "Row",
"id": "ID",
"default": "Default",
"name": "Name",
"variant": "Variant",
"headline1": "Headline 1",
"headline2": "Headline 2",
"headline3": "Headline 3",
"headline4": "Headline 4",
"headline5": "Headline 5",
"headline6": "Headline 6",
"placeholder": "Placeholder",
"required": "Required",
"min": "Min",
"max": "Max",
"min_calc": "Min Calc",
"max_calc": "Max Calc",
"add_dependency": "Add Dependency",
"add_column": "Add Column",
"mode": "Mode",
"normal": "Normal",
"paperdb": "PaperDB",
"colordb": "ColorDB",
"container": "Container",
"edit_options": "Edit Options",
"add_option": "Add Option",
"close": "Close",
"dependencies": "Dependencies",
"options": "Options",
"settings": "Settings"
}

View File

@ -0,0 +1,15 @@
import { createI18n } from 'vue-i18n'
import de from './de.json'
import en from './en.json'
const i18n = createI18n({
legacy: false,
locale: 'de',
fallbackLocale: 'en',
messages: {
de,
en
}
})
export default i18n

View File

@ -2,9 +2,11 @@ import { createApp } from 'vue'
import './style.css' import './style.css'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import i18n from './i18n'
const app = createApp(App) const app = createApp(App)
app.use(createPinia()) app.use(createPinia())
app.use(i18n)
app.mount('#app') app.mount('#app')

View File

@ -20,6 +20,10 @@ export default class BaseElement {
isFocused: boolean = false; isFocused: boolean = false;
dependencys: Dependency[] = []; dependencys: Dependency[] = [];
hasDependencys() {
return this.dependencys.length > 0;
}
toJSON() { toJSON() {
return { return {
'id': this.id, 'id': this.id,

View File

@ -7,6 +7,8 @@ export default class InputElement extends BaseElement {
name: string = "" name: string = ""
xmlType: string = "input" xmlType: string = "input"
minValue: number = 0 minValue: number = 0
minCalc: string = ""
maxCalc: string = ""
maxValue: number = 0 maxValue: number = 0
constructor() { constructor() {
@ -22,7 +24,9 @@ export default class InputElement extends BaseElement {
'default': this.default, 'default': this.default,
'name': this.name, 'name': this.name,
'minValue': this.minValue, 'minValue': this.minValue,
'minCalc': this.minCalc,
'maxValue': this.maxValue, 'maxValue': this.maxValue,
'maxCalc': this.maxCalc,
'required': this.required 'required': this.required
}) })
} }
@ -34,6 +38,8 @@ export default class InputElement extends BaseElement {
this.required = obj.required this.required = obj.required
this.placeHolder = obj.placeHolder this.placeHolder = obj.placeHolder
this.minValue = obj.minValue this.minValue = obj.minValue
this.minCalc = obj.minCalc
this.maxValue = obj.maxValue this.maxValue = obj.maxValue
this.maxCalc = obj.maxCalc
} }
} }

View File

@ -17,6 +17,17 @@ export default class SelectElement extends BaseElement {
this.options.push(option) this.options.push(option)
} }
hasDependencys() {
let hasDep = this.options.reduce((result: bool, opt: Option) => {
if(opt.dependencys.length > 0) {
result = true
}
return result
}, false)
return hasDep || super.hasDependencys()
}
public toJSON() { public toJSON() {
return Object.assign( return Object.assign(
super.toJSON(), super.toJSON(),

View File

@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
import BaseElement from '../model/BaseElement' import BaseElement from '../model/BaseElement'
import Parser from '../lib/parser' import Parser from '../lib/parser'
import {v4 as uuidv4} from 'uuid' import {v4 as uuidv4} from 'uuid'
import { loadJsonFromApi } from '../lib/api' import { loadJsonFromApi, loadPriceFromApi } from '../lib/api'
export const useElementStore = defineStore('items', { export const useElementStore = defineStore('items', {
@ -10,12 +10,17 @@ export const useElementStore = defineStore('items', {
uuid: uuidv4(), uuid: uuidv4(),
items: [] as BaseElement[], items: [] as BaseElement[],
activeItem: {} as BaseElement | {}, activeItem: {} as BaseElement | {},
formulaData: [],
formulaError: '',
isFormulaLoading: false,
showProperties: false, showProperties: false,
showDependency: false, showDependency: false,
showOptions: false,
showPreview: false, showPreview: false,
sourceDragUuid: "", sourceDragUuid: "",
dragMode: "", dragMode: "",
json: "", json: "",
xml: "",
name: uuidv4(), name: uuidv4(),
}), }),
getters: { getters: {
@ -24,9 +29,11 @@ export const useElementStore = defineStore('items', {
getActiveItem: (state) => state.activeItem as BaseElement, getActiveItem: (state) => state.activeItem as BaseElement,
isShowPropierties: (state) => state.showProperties, isShowPropierties: (state) => state.showProperties,
isShowDependency: (state) => state.showDependency, isShowDependency: (state) => state.showDependency,
isShowOptions: (state) => state.showOptions,
isShowPreview: (state) => state.showPreview, isShowPreview: (state) => state.showPreview,
getSourceDragUuid: (state) => state.sourceDragUuid, getSourceDragUuid: (state) => state.sourceDragUuid,
getDragMode: (state) => state.dragMode, getDragMode: (state) => state.dragMode,
getFormulaData: (state) => state.formulaData,
}, },
actions: { actions: {
addElement(item: BaseElement) { addElement(item: BaseElement) {
@ -73,6 +80,9 @@ export const useElementStore = defineStore('items', {
setShowDependency(value: boolean) { setShowDependency(value: boolean) {
this.showDependency = value this.showDependency = value
}, },
setShowOptions(value: boolean) {
this.showOptions = value
},
setShowProperties(value: boolean) { setShowProperties(value: boolean) {
this.showProperties = value this.showProperties = value
}, },
@ -140,10 +150,31 @@ export const useElementStore = defineStore('items', {
setDragMode(mode: string) { setDragMode(mode: string) {
this.dragMode = mode this.dragMode = mode
}, },
async loadFromApi(id: string) { async loadConfigFromProductApi(uuid: string) {
const data = await loadJsonFromApi(id); const data = await loadJsonFromApi(uuid);
this.json = data; this.json = data.json;
this.xml = data.xml;
this.parseJSON(); this.parseJSON();
},
async loadFormulaAnalyserDataFromApi(uuid: string) {
if (this.formulaData && this.formulaData.length > 0) {
return;
}
this.isFormulaLoading = true;
this.formulaError = '';
try {
const response = await loadPriceFromApi(uuid);
if (response && response.debug && response.debug.graphJson) {
this.formulaData = JSON.parse(response.debug.graphJson);
} else {
throw new Error('Invalid or empty response format from API.');
}
} catch (e: any) {
this.formulaError = `Failed to load formula data: ${e.message}`;
console.error(e);
} finally {
this.isFormulaLoading = false;
}
} }
} }

View File

@ -29,7 +29,7 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
configure: (proxy, options) => { configure: (proxy, options) => {
proxy.on('proxyReq', (proxyReq, req, res) => { proxy.on('proxyReq', (proxyReq, req, res) => {
proxyReq.setHeader('Authorization', 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3NTI1MTE3MTgsImV4cCI6MTc1MjUxNTMxOCwicm9sZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfU0hPUF9PUEVSQVRPUiIsIlJPTEVfVVNFUiIsIlJPTEVfVVNFUiIsIlJPTEVfUFNDX0NvbGxlY3RfQ29udGFjdF9FZGl0IiwiUk9MRV9QU0NfQ29sbGVjdF9Db250YWN0X0FkZCIsIlJPTEVfUFNDX0NvbGxlY3RfQ29udGFjdF9EZWxldGUiLCJST0xFX1BTQ19Db2xsZWN0X0NvbnRhY3RfTG9jayIsIlJPTEVfUFNDX1IyX1NlbmRjbG91ZF9TaG93Il0sInVpZCI6MX0.MlNFnqbWlCbaKejkcJgnri2d4pg569vfk6TrOe32rJbyyyb0X3svfhzvCsyO9i1-XwR0frm5s2fHdeGEumCjtel9VzLLIvbmr43NhUVPA03EG17pAX4QnM-GaL9vzIeZQlrIcwSprFKz9qo6Toc1Wq0mFEjvTwHj5UR5JBIgnV7TtScIVl83XJljMUbX-NrUSoOeGn6W2SRtH_bDP47ZC-P4wtDcXcrJWM9ka1Vknn-1DQgitVLtOEsxzU7bkxPpfC_ENuRqDE8HmpPZsizF4Pt9jzfAXcPy0CviBJxvg1-tu57h164VSsnz1-K6duBMTB18afi987-dXtBc7nKJhQ'); proxyReq.setHeader('Authorization', 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3NTI1Nzc2NzIsImV4cCI6MTc1MjU4MTI3Miwicm9sZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfU0hPUF9PUEVSQVRPUiIsIlJPTEVfVVNFUiIsIlJPTEVfVVNFUiIsIlJPTEVfUFNDX0NvbGxlY3RfQ29udGFjdF9FZGl0IiwiUk9MRV9QU0NfQ29sbGVjdF9Db250YWN0X0FkZCIsIlJPTEVfUFNDX0NvbGxlY3RfQ29udGFjdF9EZWxldGUiLCJST0xFX1BTQ19Db2xsZWN0X0NvbnRhY3RfTG9jayIsIlJPTEVfUFNDX1IyX1NlbmRjbG91ZF9TaG93Il0sInVpZCI6MX0.ViKfV_6cSsP6UvqibTJ5zXE8F5JZWmOEhNquw5_3bpB5N7rOtTIM8ZaD5i9VfoRf2a1CaSIKAzhte_8K49bNIpMd4B8mh3ufqUIHn33L34TsiTNblKv4PlQB_FZbCgujzfP2UenrDi3SAsHd5bynZUOJNbkGzMtv2sUot-mrKJmUvchl-yvcSFw4bYfy-88hnfXJ6sCDlIU-Vd9OSChoBqD12z3Nr6fEqY-ngq7Xv315yFY9Fhg3gxu5NqyAMzN-F6QJHjUsHlo2b9oLeAo2nFZ_Cj2nAMVa6VbxAXMrj6Ogf2POr1TWtvBBaJZj4c1D5rWktkFkPLTUAStNOwMRvQ');
}); });
}, },
}, },

View File

@ -89,6 +89,6 @@ class Config extends AbstractController
$engine->setActiveArticle($data->xmlProduct); $engine->setActiveArticle($data->xmlProduct);
} }
return $this->json($engine->generateJson()); return $this->json(['json' => $engine->generateJson(), 'xml' => $engine->generateXML(true)]);
} }
} }

View File

@ -0,0 +1,96 @@
<?php
namespace Plugin\System\PSC\XmlCalc\Api\Product;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ORM\EntityManagerInterface;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
use OpenApi\Annotations as OA;
use Plugin\System\PSC\XmlCalc\Dto\Input\DesignInput;
use Plugin\System\PSC\XmlCalc\Model\Product as PluginProduct;
use PSC\Component\ApiBundle\Dto\Error\NotFound;
use PSC\Library\Calc\Engine;
use PSC\Library\Calc\PaperContainer;
use PSC\Shop\ContactBundle\Model\Contact;
use PSC\Shop\ContactBundle\Transformer\Model\Contact as ContactTransformer;
use PSC\Shop\EntityBundle\Entity\Product;
use PSC\System\SettingsBundle\Service\PaperDB;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class Design extends AbstractController
{
public function __construct(
private \PSC\System\SettingsBundle\Service\Shop $shopService,
private DocumentManager $documentManager,
private PaperDB $paperDB,
private ContactTransformer $contactTransformer,
private TokenStorageInterface $tokenStorage,
private EntityManagerInterface $entityManager,
) {}
/**
* @OA\Response(
* response=200,
* description="get config for product",
* @OA\JsonContent(ref=@Model(type=\Plugin\System\PSC\XmlCalc\Model\Product::class))
* )
* @OA\RequestBody(
*
* @Model(type=\Plugin\System\PSC\XmlCalc\Dto\Input\DesignInput::class))
* )
* @OA\Tag(name="Plugin/System/psc/Xmlcalc/Product")
* */
#[Route(path: '/product/design', methods: ['POST'])]
#[ParamConverter(
'data',
class: '\Plugin\System\PSC\XmlCalc\Dto\Input\DesignInput',
converter: 'psc_rest.request_body',
)]
public function config(DesignInput $data)
{
$product = $this->entityManager
->getRepository('PSC\Shop\EntityBundle\Entity\Product')
->findOneBy(['uuid' => $data->product]);
$paperContainer = new PaperContainer();
$paperContainer->parse(simplexml_load_string($product->getShop()->getInstall()->getPaperContainer()));
$engine = new Engine();
$engine->setPaperRepository($this->paperDB);
$engine->setPaperContainer($paperContainer);
if ($product->getShop()->getInstall()->getCalcTemplates() && !$data->test) {
$engine->setTemplates('<root>' . $product->getShop()->getInstall()->getCalcTemplates() . '</root>');
}
if ($product->getShop()->getInstall()->getCalcTemplatesTest() && $data->test) {
$engine->setTemplates('<root>' . $product->getShop()->getInstall()->getCalcTemplatesTest() . '</root>');
}
$engine->loadJson($data->jsonProduct);
if (!$data->test) {
$engine->setFormulas($product->getShop()->getFormel());
$engine->setParameters($product->getShop()->getParameter());
}
if ($data->test) {
$engine->setFormulas($product->getShop()->getTestFormel());
$engine->setParameters($product->getShop()->getTestParameter());
}
$engine->setVariables($data->values);
$engine->setTax($product->getMwert());
if ($this->tokenStorage->getToken()) {
$contact = new Contact();
$this->contactTransformer->fromDb($contact, $this->tokenStorage->getToken()->getUser());
$engine->setVariable('contact.accountType', $contact->getAccountType()->value);
$engine->setVariable('contact.account', $contact->getAccount()->getUid());
}
return $this->json([
'json' => $engine->generateJson(),
'xml' => $engine->generateXML(true),
'jsonGraph' => $engine->getCalcGraph()->generateJsonGraph(),
'price' => $engine->getPrice() * 100,
]);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Plugin\System\PSC\XmlCalc\Dto\Input;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Annotations as OA;
final class DesignInput
{
/**
* @var string
*
* @OA\Property(type="string")
*/
public string $product;
/**
* @var string
*
* @OA\Property(type="string")
*/
public string $jsonProduct = '';
/**
* @var bool
*
* @OA\Property(type="boolean")
*/
public bool $test = false;
/**
* @var array
*
* @OA\Property(type="array", @OA\Items(type="string"))
*/
public array $values = ['auflage' => 100];
}