stand2026_10_06

This commit is contained in:
marco committed 2026-10-06 15:34:27 +02:00
1 parent 41f80ce947
commit 266f8c3b4f
199 files changed
+17043 -69

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
{
}
+151 -3
View File
@@ -8,9 +8,18 @@
"name": "rk-frontend", "name": "rk-frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@radix-ui/react-slot": "^1.3.3",
"@xmldom/xmldom": "^0.9.11",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.34.0",
"next": "16.1.6", "next": "16.1.6",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3" "react-dom": "19.2.3",
"react-hook-form": "^7.71.2",
"tailwind-merge": "^3.6.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
@@ -33,6 +42,55 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
"dependencies": {
"@dnd-kit/accessibility": "^3.1.1",
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@dnd-kit/sortable": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
"integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
"dependencies": {
"@dnd-kit/utilities": "^3.2.2",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@dnd-kit/core": "^6.3.0",
"react": ">=16.8.0"
}
},
"node_modules/@dnd-kit/utilities": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
@@ -653,6 +711,37 @@
"node": ">= 10" "node": ">= 10"
} }
}, },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz",
"integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz",
"integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.5"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@swc/helpers": { "node_modules/@swc/helpers": {
"version": "0.5.15", "version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -930,7 +1019,7 @@
"version": "19.2.14", "version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -944,6 +1033,14 @@
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
}, },
"node_modules/@xmldom/xmldom": {
"version": "0.9.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.11.tgz",
"integrity": "sha512-tW8bcK3hsG0/uqSnNz6TK4BkcuZSezoU7DlnYssILmZDktPnSHHuDJJFM0AJv+13gz2r0iGdrj6qqKeUnxXEDg==",
"engines": {
"node": ">=14.6"
}
},
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.10.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
@@ -974,16 +1071,35 @@
} }
] ]
}, },
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
"dependencies": {
"clsx": "^2.1.1"
},
"funding": {
"url": "https://polar.sh/cva"
}
},
"node_modules/client-only": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true "devOptional": true
}, },
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.1.2", "version": "2.1.2",
@@ -1271,6 +1387,14 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/lucide-react": {
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.34.0.tgz",
"integrity": "sha512-vnjGJNI7Htk5+oWW8gXGuaLgwgAb0T6/iZbBrp9JCfRFwdNWZ0YTm3eyxjOLgwN6r8iyAf3UA70zNmBRBNv7yg==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -1428,6 +1552,21 @@
"react": "^19.2.3" "react": "^19.2.3"
} }
}, },
"node_modules/react-hook-form": {
"version": "7.71.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz",
"integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==",
"engines": {
"node": ">=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -1519,6 +1658,15 @@
} }
} }
}, },
"node_modules/tailwind-merge": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
"integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz",
+11 -2
View File
@@ -5,12 +5,21 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start -p 4010"
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@radix-ui/react-slot": "^1.3.3",
"@xmldom/xmldom": "^0.9.11",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.34.0",
"next": "16.1.6", "next": "16.1.6",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3" "react-dom": "19.2.3",
"react-hook-form": "^7.71.2",
"tailwind-merge": "^3.6.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
+171
View File
@@ -0,0 +1,171 @@
<mxfile host="65bd71144e">
<diagram id="Bpvn5WHq1YyJgIW7uXgr" name="Page-1">
<mxGraphModel dx="1354" dy="462" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="MainPage" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="150" y="130" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="5" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="3" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="400" y="215" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="3" value="SelectAnlage" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="160" y="200" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="4" value="NewAnlage" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="160" y="240" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="6" value="Anlagen Übersicht" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="400" y="130" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="7" value="Gruppen" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="405" y="200" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="8" value="Elemente" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="405" y="240" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="9" value="EA&#39;S" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="405" y="280" width="110" height="30" as="geometry"/>
</mxCell>
<mxCell id="10" value="xaml&lt;div&gt;von Procon&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="120" y="430" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="14" value="" style="edgeStyle=none;html=1;" parent="1" source="11" target="13" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="svg/xaml&lt;div&gt;creator&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="120" y="530" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="13" value="Erzeugt&lt;div&gt;XAML/SVG&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="350" y="530" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="15" value="Visu/Web" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="120" y="700" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="17" value="RK" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="590" y="700" width="60" height="80" as="geometry"/>
</mxCell>
<mxCell id="18" value="Holt SVG Daten" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="380" y="700" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="19" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="18" target="17" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="20" value="Backend" style="whiteSpace=wrap;html=1;rounded=0;" parent="1" vertex="1">
<mxGeometry x="380" y="670" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="21" value="" style="swimlane;startSize=0;" parent="1" vertex="1">
<mxGeometry x="160" y="860" width="380" height="200" as="geometry"/>
</mxCell>
<mxCell id="23" value="" style="endArrow=none;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="620" y="1000" as="sourcePoint"/>
<mxPoint x="640" y="880" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="24" value="" style="endArrow=none;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="640" y="880" as="sourcePoint"/>
<mxPoint x="800" y="850" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="25" value="" style="endArrow=none;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="620" y="1000" as="sourcePoint"/>
<mxPoint x="820" y="1000" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="26" value="" style="endArrow=none;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="800" y="850" as="sourcePoint"/>
<mxPoint x="820" y="1000" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="27" value="" style="endArrow=none;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="640" y="880" as="sourcePoint"/>
<mxPoint x="820" y="1000" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="28" value="" style="endArrow=none;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="800" y="850" as="sourcePoint"/>
<mxPoint x="620" y="1000" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="29" value="" style="endArrow=none;html=1;" parent="1" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="640" y="880" as="sourcePoint"/>
<mxPoint x="630" y="1000" as="targetPoint"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
<diagram id="cASbCO5rFMgaGpc82weQ" name="Page-2">
<mxGraphModel dx="1354" dy="634" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="130gX1RrYmnBiqMe9CBA-1" value="Unit" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="365" y="220" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-2" value="Component&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="365" y="330" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-3" value="&lt;div&gt;Properties&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="365" y="450" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-4" value="&lt;div&gt;Signals&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="620" y="390" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-5" value="&lt;div&gt;_id&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="365" y="360" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-6" value="&lt;div&gt;_id&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="365" y="280" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-14" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="130gX1RrYmnBiqMe9CBA-7" target="130gX1RrYmnBiqMe9CBA-8">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-15" value="n:1" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="130gX1RrYmnBiqMe9CBA-14">
<mxGeometry x="-0.0322" y="-2" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-7" value="&lt;div&gt;signalIds[]&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="365" y="390" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-8" value="&lt;div&gt;_id&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="620" y="420" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-13" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="130gX1RrYmnBiqMe9CBA-9" target="130gX1RrYmnBiqMe9CBA-12">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-16" value="1:n" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="130gX1RrYmnBiqMe9CBA-13">
<mxGeometry x="0.1" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-9" value="&lt;div&gt;ioId&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="620" y="520" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-10" value="&lt;div&gt;name&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="620" y="490" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-11" value="&lt;div&gt;IOs&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="820" y="390" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-12" value="&lt;div&gt;_id&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="820" y="420" width="120" height="30" as="geometry"/>
</mxCell>
<mxCell id="130gX1RrYmnBiqMe9CBA-17" value="&lt;div&gt;componentId&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="620" y="450" width="120" height="30" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
+73
View File
@@ -0,0 +1,73 @@
import { difference } from "next/dist/build/utils"
const rkPrefix = '/rk'
export const Sites = {
main: { url: '/', name: 'Home' },
anlagen: {
url: `${rkPrefix}/anlagen`,
name: 'Anlagen',
new: { url: `${rkPrefix}/anlagen/new` },
update: { url: `${rkPrefix}/anlagen/update`, param: 'id' }
},
groups: {
url: `${rkPrefix}/groups`,
name: 'Gruppen',
new: { url: `${rkPrefix}/groups/new`, name:'Neue Gruppe' },
update: { url: `${rkPrefix}/groups/update`, param: 'id' }
},
elements: {
url: `${rkPrefix}/elements`,
name: 'Elemente',
new: { url: `${rkPrefix}/elements/new`, name:'Neues Element Anlegen' },
update: { url: `${rkPrefix}/elements/update`, param: 'id' }
},
peripheries: {
url: `${rkPrefix}/peripheries`,
name: 'Ein/Ausgänge',
new: { url: `${rkPrefix}/peripheries/new` },
update: { url: `${rkPrefix}/peripheries/update`, param: 'id' },
differences: { url: `${rkPrefix}/peripheries/differences`, name: "Unterschiede EA Liste -Excel" }
},
logNote: {
url: `${rkPrefix}/log-note`,
name: 'Notizen',
new: { url: `${rkPrefix}/log-note/new`,name:"Neue Notiz" },
update: { url: `${rkPrefix}/log-note/update`, name:"Notiz Bearbeiten", param: 'id' },
},
visu: {
url: `visu`,
name: 'Visualisierung',
create: { url: `visu/create`, name:"Create Element" },
elements: { url: `visu/elements`, name:"Visu Elemente" },
selectGroups: { url: `visu/groups_add`, name:"Gruppen zu Visu hinzufügen" },
},
procon: {
url: `/procon`,
name: 'DB Procon',
aggregate: { url: `/procon/aggregate`, name:"Übersicht Aggregate" },
texteVisu: { url: `/procon/visu_texte`, name:"Visualisierung Texte" },
sensoren: { url: `/procon/sensoren`, name:"Visualisierung Texte" },
anzeigen: { url: `/procon/anzeigen`, name:"Visualisierung Anzeigen" },
svgTest: { url: `/procon/svg`, name:"SVG test" },
},
rk2: {
url: `/rk2`,
name: 'Anlagen (neu-RK2)',
groups: {
url: `/rk2/groups`, name:"Gruppen RK2" ,
new:{url: `/rk2/groups/edit/new`, name:"Neue Gruppe" },
edit:{url: `/rk2/groups/edit`, name:"Gruppe Ändern" }
},
units: {
url: `/rk2/units`,
name:"Units",
new:{url: `/rk2/units//edit/new`, name:"Neue Unit" }
},
signals: { url: `/rk2/signale`, name:"Signale" },
ios: { url: `/rk2/ios`, name:"IO " },
}
}
+48
View File
@@ -0,0 +1,48 @@
import { VisuElement } from "@/types/visu/visuElements.types";
export type DragMode = "idle" | "canvas" | "element";
export interface HistoryState<T> {
past: T[];
present: T;
future: T[];
}
export interface DragState {
mode: DragMode;
elementId?: string;
startX: number;
startY: number;
lastX: number;
lastY: number;
}
export type VisuElementsState = {
drag: DragState;
elements: VisuElement[];
selectedElement: VisuElement | undefined;
editSession: {
active: boolean;
source: "drag" | "panel" | null;
};
test: string;
};
export type VisuHistoryState = HistoryState<VisuElementsState>;
export type VisuElementsActions =
| { type: "SET_ELEMENTS"; payload: VisuElement[] }
| { type: "SELECT_ELEMENT"; payload: VisuElement }
| { type: "DESELECT_ELEMENT" }
| { type: "DRAG_START"; payload: { mode: "canvas" | "element"; elementId?: string; x: number; y: number } }
| { type: "DRAG_MOVE"; payload: { x: number; y: number } }
| { type: "DRAG_END" }
| { type: "MOVE_SELECTED_ELEMENT"; payload: { dx: number, dy: number } }
| { type: "UPDATE_ELEMENT"; payload: VisuElement }
| { type: "UNDO" }
| { type: "REDO" }
| { type: "SET_STATE"; payload: VisuElementsState }
| { type: "UPDATE_SCALE"; payload: number }
| { type: "UPDATE_LINE_OFFSET"; payload: Partial<{x:number,y:number}> }
;
+9
View File
@@ -0,0 +1,9 @@
import { createContext, Dispatch } from "react";
import { VisuElementsActions, VisuElementsState } from "./VisuElements.types";
type VisuElementsContextType = {
state: VisuElementsState;
dispatch: Dispatch<VisuElementsActions>;
};
export const VisuElementsContext = createContext<VisuElementsContextType | undefined>(undefined);
@@ -0,0 +1,37 @@
"use client";
import { useEffect, useReducer } from "react";
import { VisuElementsState } from "./VisuElements.types";
import { VisuElementsReducer } from "./VisuElementsReducer";
import { VisuElementsContext } from "./VisuElementsContext";
import { VisuElement } from "@/types/visu/visuElements.types";
const initialState: VisuElementsState = {
drag: {
mode: "idle",
startX: 0,
startY: 0,
lastX: 0,
lastY: 0,
},
elements: [],
selectedElement: undefined,
editSession: {active:false,source:null},
test: "",
};
export function VisuElementsProvider({ children, initialValues }: { children: React.ReactNode, initialValues: VisuElement[] }) {
const [state, dispatch] = useReducer(VisuElementsReducer, initialState);
useEffect(() => {
dispatch({ type: "SET_ELEMENTS", payload: initialValues })
}, [initialValues])
return (
<VisuElementsContext.Provider value={{ state, dispatch }}>
{children}
</VisuElementsContext.Provider>
);
}
+180
View File
@@ -0,0 +1,180 @@
import { stat } from "fs";
import { VisuElementsActions, VisuElementsState } from "./VisuElements.types";
const helper = (state:VisuElementsState):VisuElementsState=>{
return state;
}
export function VisuElementsReducer(state: VisuElementsState, action: VisuElementsActions): VisuElementsState {
switch (action.type) {
// -----------------------
// SET ELEMENTS
// -----------------------
case "SET_ELEMENTS":
return {
...state,
elements: action.payload,
test: "daten wurden gleaden"
}
// -----------------------
// SELECT
// -----------------------
case "SELECT_ELEMENT":
return {
...state,
selectedElement: action.payload,
};
case "DESELECT_ELEMENT":
return {
...state,
selectedElement: undefined,
};
// -----------------------
// DRAG START
// -----------------------
case "DRAG_START":
return {
...state,
drag: {
mode: action.payload.mode,
elementId: action.payload.elementId,
startX: action.payload.x,
startY: action.payload.y,
lastX: action.payload.x,
lastY: action.payload.y,
},
};
// -----------------------
// DRAG MOVE
// -----------------------
case "DRAG_MOVE": {
const drag = state.drag;
const dx = action.payload.x - drag.lastX;
const dy = action.payload.y - drag.lastY;
if (state.drag.mode !== "element") return state;
// -----------------------
// ELEMENT DRAG
// -----------------------
if (drag.mode === "element" && drag.elementId) {
let updatedElement: any = null;
const elements = state.elements.map((el) => {
if (el.id !== drag.elementId) return el;
const newX = el.posX + dx;
const newY = el.posY + dy;
updatedElement = {
...el,
posX: newX,
posY: newY,
};
return updatedElement;
});
return {
...state,
elements,
drag: {
...drag,
lastX: action.payload.x,
lastY: action.payload.y,
},
selectedElement:
state.selectedElement?.id === drag.elementId
? updatedElement
: state.selectedElement,
};
}
// -----------------------
// CANVAS DRAG
// -----------------------
if (drag.mode === "canvas") {
return {
...state,
drag: {
...drag,
lastX: action.payload.x,
lastY: action.payload.y,
},
// ❗ KEIN state change hier
};
}
return state;
}
// -----------------------
// DRAG END (snap)
// -----------------------
case "DRAG_END":
return {
...state,
drag: {
mode: "idle",
elementId: undefined,
startX: 0,
startY: 0,
lastX: 0,
lastY: 0,
},
};
// -----------------------
// KEYBOARD MOVE
// -----------------------
case "MOVE_SELECTED_ELEMENT":
if (!state.selectedElement) return state;
const moved = {
...state.selectedElement,
posX: state.selectedElement.posX + action.payload.dx,
posY: state.selectedElement.posY + action.payload.dy,
};
return {
...state,
selectedElement: moved,
elements: state.elements.map((el) =>
el.id === moved.id ? moved : el
),
};
// -----------------------
// UPDATE
// -----------------------
case "UPDATE_ELEMENT":
const updated =state.elements.map(el =>
el.id === action.payload.id ? action.payload : el
);
return {
...state,
elements: updated,
selectedElement:
state.selectedElement?.id === action.payload.id
? action.payload
: state.selectedElement,
};
// -----------------------
// UPDATE
// -----------------------
case "UPDATE_LINE_OFFSET":
return state;
default:
return state;
}
}
@@ -0,0 +1,13 @@
import { useContext } from "react";
import { VisuElementsContext } from "./VisuElementsContext";
export function useVisuElementContext() {
const context = useContext(VisuElementsContext);
if (!context) {
throw new Error("useFormContext must be used within FormProvider");
}
return context;
}
+22
View File
@@ -0,0 +1,22 @@
"use server"
const url = "http://localhost:3000/draw";
//-----------------------------------------------------------------------------
//--- Get Data of Grafik
//-----------------------------------------------------------------------------
export async function api_drawData(name?:string): Promise<any> {
name =name ? name: "anlageDurmin";
const result = await fetch(`${url}/${name}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return (data);
}
@@ -0,0 +1,38 @@
"use server"
import { revalidatePath } from "next/cache";
import { Sites } from "@/_config/sites";
import { ProConAggregate, ProConAggVisu } from "@/types/procon";
const url = "http://localhost:3000/procon/";
const url_aggrgate = url + "aggregate"
export async function api_ProconAggregate_GetAll(): Promise<ProConAggregate[]> {
const result = await fetch(`${url_aggrgate}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return orderById(data);
}
export async function api_ProconAggregate_GetById(id:number): Promise<ProConAggVisu> {
const result = await fetch(`${url_aggrgate}/byAntriebsId/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
function orderById(data:ProConAggregate[]) {
return data.sort((a,b)=>a.SPSAntriebId-b.SPSAntriebId)
}
+64
View File
@@ -0,0 +1,64 @@
"use server"
import { ProConAnzeige, ProConAnzeigeVisuElement } from "@/types/procon/anzeigen";
const url = "http://localhost:3000/procon/";
const url_anzeigen = url + "anzeigen"
//-----------------------------------------------------------------------------
//--- Anzeigen
//-----------------------------------------------------------------------------
export async function api_ProconAnzeigen(): Promise<ProConAnzeige[]> {
const result = await fetch(`${url_anzeigen}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return orderByDatenNummer(data);
}
export async function api_ProconAnzeige_byId(id:number): Promise<ProConAnzeige> {
const result = await fetch(`${url_anzeigen}/byId/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_ProconAnzeige_byDatenNumber(id:number): Promise<ProConAnzeige> {
const result = await fetch(`${url_anzeigen}/byDatenNummer/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//--- Visu Elemente
export async function api_ProconAnzeige_visuElements(id:number): Promise<ProConAnzeigeVisuElement[]> {
const result = await fetch(`${url_anzeigen}/VisuElements/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
function orderByDatenNummer(data:ProConAnzeige[]) {
return data.sort((a,b)=>a.DatenNummer-b.DatenNummer)
}
@@ -0,0 +1,50 @@
"use server"
import { ProConMeldung } from "@/types/procon/meldungen";
const url = "http://localhost:3000/procon/";
const url_meldungen = url + "meldungen"
//-----------------------------------------------------------------------------
//--- Meldungen
//-----------------------------------------------------------------------------
export async function api_ProconMeldungen(): Promise<ProConMeldung[]> {
const result = await fetch(`${url_meldungen}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_ProconMeldung_byId(id:number): Promise<ProConMeldung> {
const result = await fetch(`${url_meldungen}/byId/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_ProconMeldung_byAggregateId(id:number): Promise<ProConMeldung[]> {
const result = await fetch(`${url_meldungen}/byAggregateId/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
+117
View File
@@ -0,0 +1,117 @@
"use server"
import { ProConSensor, ProConSensorVisuElement } from "@/types/procon/sensoren";
const url = "http://localhost:3000/procon/";
const url_sensoren = url + "sensoren"
const digital ="digital";
const analog = "analog";
//-----------------------------------------------------------------------------
//--- Digital Sensoren
//-----------------------------------------------------------------------------
export async function api_ProconSensoren_digital(): Promise<ProConSensor[]> {
const result = await fetch(`${url_sensoren}/${digital}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return orderByDatenNummer(data);
}
export async function api_ProconSensor_digital_byId(id:number): Promise<ProConSensor> {
const result = await fetch(`${url_sensoren}/${digital}/byId/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_ProconSensor_digital_byTextNumber(id:number): Promise<ProConSensor> {
const result = await fetch(`${url_sensoren}/${digital}/byDatenNummer/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//--- Visu Elemente
export async function api_ProconSensor_digital_visuElements(id:number): Promise<ProConSensorVisuElement[]> {
const result = await fetch(`${url_sensoren}/${digital}/VisuElements/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//-----------------------------------------------------------------------------
//--- Analog Sensoren
//-----------------------------------------------------------------------------
export async function api_ProconSensoren_analog(): Promise<ProConSensor[]> {
const result = await fetch(`${url_sensoren}/${analog}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return orderByDatenNummer(data);
}
export async function api_ProconSensor_analog_byId(id:number): Promise<ProConSensor> {
const result = await fetch(`${url_sensoren}/${analog}/byId/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_ProconSensor_analog_byTextNumber(id:number): Promise<ProConSensor> {
const result = await fetch(`${url_sensoren}/${analog}/byDatenNummer/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//--- Visu Elemente
export async function api_ProconSensor_analog_visuElements(id:number): Promise<ProConSensorVisuElement[]> {
const result = await fetch(`${url_sensoren}/${analog}/VisuElements/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
function orderByDatenNummer(data:ProConSensor[]) {
return data.sort((a,b)=>a.DatenNummer-b.DatenNummer)
}
+48
View File
@@ -0,0 +1,48 @@
"use server"
import { ProConText } from "@/types/procon/texte";
const url = "http://localhost:3000/procon/";
const url_texte = url + "texte"
export async function api_ProconTexte_getTexte(): Promise<ProConText[]> {
const result = await fetch(`${url_texte}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return orderByNummer(data);
}
export async function api_ProconTexte_byId(id:number): Promise<ProConText> {
const result = await fetch(`${url_texte}/byId/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_ProconTexte_byTextNumber(id:number): Promise<ProConText> {
const result = await fetch(`${url_texte}/byTextNumber/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
function orderByNummer(data:ProConText[]) {
return data.sort((a,b)=>a.Nummer-b.Nummer)
}
+34
View File
@@ -0,0 +1,34 @@
"use server"
import { revalidatePath } from "next/cache";
import { Sites } from "@/_config/sites";
const url = "http://localhost:3000/procon/";
const url_views = url + "views"
export async function api_ProconViews_UpdateXAML(id: string, xamlBase64: string): Promise<ActionResult> {
let updateData = {
"xamlBase64": xamlBase64
}
console.log(updateData,id)
const result = await fetch(`${url_views}/xaml/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updateData),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.procon.url);
return { success: true };
}
@@ -0,0 +1,39 @@
"use server"
import { revalidatePath } from "next/cache";
import { Sites } from "@/_config/sites";
import { ProConAggregate, ProConView } from "@/types/procon";
const url = "http://localhost:3000/procon/";
const url_visualisierungen = url + "visu"
export async function api_ProconVisualisierungen_Update(id: number, data: Partial<ProConView>): Promise<ActionResult> {
let updateData = {
"PositionX": data.posX,
"PositionY": data.posY,
"Height": data.height,
"Width": data.width,
"PositionZ":data.zIndex,
}
console.log(updateData,id)
const result = await fetch(`${url_visualisierungen}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updateData),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.procon.url);
return { success: true };
}
+53
View File
@@ -0,0 +1,53 @@
'use server'
import { Sites } from "@/_config/sites";
import { Anlage } from "@/types/rk";
import { revalidatePath } from "next/cache";
export async function api_anlagen_getAll(): Promise<Anlage[]> {
const result = await fetch(`http://localhost:3000/anlage`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_anlage_getSelected(): Promise<Anlage> {
const result = await fetch(`http://localhost:3000/anlage/selected`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_anlage_setSelected(id: string): Promise<Anlage> {
console.log(`http://localhost:3000/anlage/selected/${id}`);
const result = await fetch(`http://localhost:3000/anlage/selected/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
console.log(result.status, result.statusText);
if (!result.ok) {
const error = await result.text();
throw new Error(`API Fehler ${result.status}: ${error}`);
}
revalidatePath(Sites.anlagen.url);
return data;
}
+28
View File
@@ -0,0 +1,28 @@
import { DrawShapeData, DrawShapeOptionen } from "@/types/draw/draw.types";
import { Anlage } from "@/types/rk";
export async function drawGetSvg(name: string, optionen?: DrawShapeOptionen): Promise<DrawShapeData> {
let optionenStr = '';
if (optionen) {
optionenStr = '?'
let separator = ''
for (const [key, value] of Object.entries(optionen)) {
optionenStr += separator + key + "=" + value;
separator = '&'
}
console.log(optionenStr)
}
const result = await fetch(`http://localhost:3000/draw/gurtfoerderer${optionenStr}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json() as DrawShapeData;
return data;
}
+96
View File
@@ -0,0 +1,96 @@
"use server"
import { Sites } from "@/_config/sites";
import { Element, ElementCreate, ElementFilterOptionen, ElementUpdate } from "@/types/rk";
import { revalidatePath } from "next/cache";
export async function api_ElementsGet(filter?: ElementFilterOptionen): Promise<Element[]> {
let filterStr = '';
if (filter) {
filterStr = '?'
let separator = ''
for (const [key, value] of Object.entries(filter)) {
filterStr += separator + key + "=" + value;
separator = '&'
}
}
const result = await fetch(`http://localhost:3000/element${filterStr}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function elementCreate(data: ElementCreate): Promise<ActionResult<Element>> {
const result = await fetch('http://localhost:3000/element', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.elements.url+"/")
return { success: true };
}
export async function elementUpdate(id: string, data: ElementUpdate): Promise<ActionResult<Element>> {
const result = await fetch(`http://localhost:3000/element/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.elements.url);
return { success: true };
}
export async function api_elementDelete(id: string): Promise<ActionResult<Element>> {
const result = await fetch(`http://localhost:3000/element/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.elements.url);
return { success: true };
}
+24
View File
@@ -0,0 +1,24 @@
'use server';
export async function uploadFile(formData: FormData) {
const file = formData.get('file') as File;
if (!file) {
throw new Error('Keine Datei');
}
// Neues FormData für NestJS
const backendForm = new FormData();
backendForm.append('file', file);
const res = await fetch('http://localhost:3000/draw/upload', {
method: 'POST',
body: backendForm,
});
if (!res.ok) {
throw new Error('Upload fehlgeschlagen');
}
return await res.json();
}
+97
View File
@@ -0,0 +1,97 @@
"use server"
import { Sites } from "@/_config/sites";
import { Group, GroupCreate, GroupFilterOptionen, GroupUpdate } from "@/types/rk";
import { revalidatePath } from "next/cache";
export async function api_GroupAll(filter?: GroupFilterOptionen): Promise<Group[]> {
let filterStr = '';
if (filter) {
filterStr = '?'
let separator = ''
for (const [key, value] of Object.entries(filter)) {
filterStr += separator + key + "=" + value;
separator = '&'
}
console.log(filterStr)
console.log(`http://localhost:3000/group${filter}`);
}
const result = await fetch(`http://localhost:3000/group${filterStr}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function groupCreate(data: GroupCreate): Promise<ActionResult<Group>> {
const result = await fetch('http://localhost:3000/group', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.groups.url)
return { success: true,data:resultData };
}
export async function groupUpdate(id: string, data: GroupUpdate): Promise<ActionResult<Group>> {
const result = await fetch(`http://localhost:3000/group/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.groups.url);
return { success: true,data:resultData };
}
export async function api_groupDelete(id: string): Promise<ActionResult<Group>> {
console.log('delete Group')
const result = await fetch(`http://localhost:3000/group/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.groups.url);
return { success: true,data:resultData };
}
+97
View File
@@ -0,0 +1,97 @@
"use server"
import { Sites } from "@/_config/sites";
import { LogNote, LogNoteCreate, LogNoteFilterOptionen, LogNoteUpdate } from "@/types/rk";
import { revalidatePath } from "next/cache";
const path ="log-note"
export async function api_LogNoteGet(filter?: LogNoteFilterOptionen): Promise<LogNote[]> {
let filterStr = '';
if (filter) {
filterStr = '?'
let separator = ''
for (const [key, value] of Object.entries(filter)) {
filterStr += separator + key + "=" + value;
separator = '&'
}
}
const result = await fetch(`http://localhost:3000/${path}${filterStr}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_logNoteCreate(data: LogNoteCreate): Promise<ActionResult<LogNote>> {
const result = await fetch(`http://localhost:3000/${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
// revalidatePath(Sites.elements.url)
return { success: true };
}
export async function api_logNoteUpdate(id: string, data: LogNoteUpdate): Promise<ActionResult<LogNote>> {
const result = await fetch(`http://localhost:3000/${path}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.logNote.url);
return { success: true };
}
export async function api_logNoteDelete(id: string): Promise<ActionResult<LogNote>> {
const result = await fetch(`http://localhost:3000/${path}/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.logNote.url);
return { success: true };
}
+60
View File
@@ -0,0 +1,60 @@
"use server";
import { Sites } from "@/_config/sites";
import { Group, Peripherie, PeripherieFilterOptionen, PeripherieUpdate } from "@/types/rk";
import { revalidatePath } from "next/cache";
export async function PeripherieFindApi(filter?: PeripherieFilterOptionen): Promise<Peripherie[]> {
let filterStr = '';
if (filter && (filter.adrByte || filter.elementId || filter.groupId)) {
filterStr = '?'
if (filter.adrByte) filterStr += 'adrByte=' + filter.adrByte;
if (filter.groupId) filterStr += 'groupId=' + filter.groupId;
if (filter.elementId) filterStr += 'elementId=' + filter.elementId;
}
console.log(`http://localhost:3000/peripherie` + filterStr)
const result = await fetch(`http://localhost:3000/peripherie` + filterStr, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function GetEA_Differences(): Promise<Peripherie[]> {
const result = await fetch(`http://localhost:3000/peripherie/differences`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
export async function api_peripherieUpdate(id: string, data: PeripherieUpdate): Promise<ActionResult<Peripherie>> {
const result = await fetch(`http://localhost:3000/peripherie/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.peripheries.url);
return { success: true };
}
+64
View File
@@ -0,0 +1,64 @@
'use server'
import { Sites } from "@/_config/sites";
import { VisuElement, VisuElementCreate, VisuElementUpdate } from "@/types/visu/visuElements.types";
import { revalidatePath } from "next/cache";
export async function api_VisuElementsAll(): Promise<VisuElement[]> {
const result = await fetch(`http://localhost:3000/visu-elements`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json() as VisuElement[];
return data;
}
export async function api_VisuElementCreate(data: VisuElementCreate): Promise<ActionResult<VisuElement>> {
const result = await fetch(`http://localhost:3000/visu-elements/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.visu.url);
return { success: true };
}
export async function api_VisuElementUpdate(id: string, data: VisuElementUpdate): Promise<ActionResult<VisuElement>> {
const result = await fetch(`http://localhost:3000/visu-elements/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.visu.url);
return { success: true };
}
+481
View File
@@ -0,0 +1,481 @@
import { ComponentDefaults, ComponentTypeDefinition } from "@/types/anlagen/components";
import { SignalType } from "@/types/anlagen/signals";
export const componentTypeDefinitions: ComponentTypeDefinition[] = [
{
type: "Antrieb",
label: "Antrieb",
properties: [
{
key: "linkslauf",
label: "Linkslauf",
type: "boolean",
defaultValue: false,
required: false,
},
{
key: "direkt",
label: "Direkt",
type: "boolean",
defaultValue: true,
required: false,
},
{
key: "sternDreieck",
label: "Ster-Dreieck",
type: "boolean",
defaultValue: false,
required: false,
},
{
key: "fu",
label: "Frequenzumrichter",
type: "boolean",
defaultValue: false,
required: false,
},
{
key: "rm",
label: "Rueckmeldung",
type: "boolean",
defaultValue: true,
required: false,
},
{
key: "ms",
label: "Motorschutz",
type: "boolean",
defaultValue: true,
required: false,
},
{
key: "ptc",
label: "PTC",
type: "boolean",
defaultValue: false,
required: false,
},
{
key: "ts",
label: "Thermoschutz",
type: "boolean",
defaultValue: false,
required: false,
},
],
},
{
type: "Frequenzumrichter",
label: "Frequenzumrichter",
properties: [
{
key: "manufacturer",
label: "Hersteller",
type: "text",
defaultValue: 'Schneider',
},
{
key: "power",
label: "Leistung [kW]",
type: "number",
defaultValue: "5",
},
{
key: "maxFrequency",
label: "Max. Frequenz [Hz]",
type: "number",
defaultValue: 50,
},
],
},
{
type: "Reparaturschalter",
label: "Reparaturschalter",
properties: [
],
},
{
type: "Endschalter",
label: "Endschalter",
properties: [
{
key: "normallyClosed",
label: "Öffner",
type: "boolean",
defaultValue: false,
},
],
},
{
type: "Schieflaufwächter",
label: "Schieflaufwächter",
properties: [
{
key: "warnung",
label: "Warnung",
type: "boolean",
defaultValue: false,
},
],
},
{
type: "Reißleine",
label: "Reißleine",
properties: [
{
key: "safty",
label: "Safety",
type: "boolean",
defaultValue: true,
},
],
},
{
type: "Drehzahl",
label: "Drehzahl",
properties: [
{
key: "impuls",
label: "Impulssignal",
type: "boolean",
defaultValue: true,
},
{
key: "dauer",
label: "Dauersignal",
type: "boolean",
defaultValue: false,
},
],
},
{
type: "Vorortbedienung",
label: "Vorortbedienung",
properties: [
{
key: "ta_ein",
label: "Ein Taster",
type: "boolean",
defaultValue: true,
},
{
key: "ta_ein_rtg2",
label: "Taster Ein Richtung 2",
type: "boolean",
defaultValue: false,
},
{
key: "ta_aus_no",
label: "Taster Aus (no)",
type: "boolean",
defaultValue: false,
},
{
key: "ta_aus_nc",
label: "Taster Aus (nc)",
type: "boolean",
defaultValue: false,
}
],
},
{
type: "Fuellstand",
label: "Füllstand",
properties: [
],
},
{
type: "Überwachung",
label: "Überwachung",
properties: [
{
key: "monitoringType",
label: "Überwachungstyp",
type: "select",
options: [
{
value: "strom",
label: "Strom",
},
{
value: "temperature",
label: "Temperatur",
},
{
value: "pressure",
label: "Druck",
},
{
value: "vibration",
label: "Vibration",
},
],
defaultValue: "strom",
},
],
},
]
export const componentDefaults: ComponentDefaults[] = [
{
type: "Antrieb",
name: "M",
signals: [
{ name: "start", type: SignalType.DIGITAL_OUTPUT },
{ name: "startRtg2", type: SignalType.DIGITAL_OUTPUT },
{ name: "rm", type: SignalType.DIGITAL_INPUT },
{ name: "msOk", type: SignalType.DIGITAL_INPUT },
{ name: "tsOk", type: SignalType.DIGITAL_INPUT },
{ name: "ptcOk", type: SignalType.DIGITAL_INPUT },
],
},
{
type: "Frequenzumrichter",
name: "Fu",
signals: [],
},
{
type: "Reparaturschalter",
name: "Hs",
signals: [
{ name: "HsEin", type: SignalType.DIGITAL_INPUT },
],
},
{
type: "Endschalter",
name: "Es",
signals: [
{ name: "Pos", type: SignalType.DIGITAL_INPUT },
],
},
{
type: "Schieflaufwächter",
name: "Sl",
signals: [
{ name: "SlOk", type: SignalType.DIGITAL_INPUT },
{ name: "Sl_Warnung", type: SignalType.DIGITAL_INPUT },
],
},
{
type: "Reißleine",
name: "Rl",
signals: [
{ name: "RlOk", type: SignalType.DIGITAL_INPUT },
{ name: "RlQuitt", type: SignalType.DIGITAL_OUTPUT },
],
},
{
type: "Drehzahl",
name: "Dz",
signals: [
{ name: "DzOk", type: SignalType.DIGITAL_INPUT },
{ name: "DzImpuls", type: SignalType.DIGITAL_INPUT },
],
},
{
type: "Vorortbedienung",
name: "Vorort",
signals: [
{ name: "TaEin", type: SignalType.DIGITAL_INPUT },
{ name: "TaNichtAus", type: SignalType.DIGITAL_INPUT },
],
},
{
type: "Überwachung",
name: "Ls",
signals: [
{ name: "MaxOk", type: SignalType.DIGITAL_INPUT },
{ name: "Min", type: SignalType.DIGITAL_INPUT },
],
},
{
type: "Fuellstand",
name: "Fuellstand",
signals: [
{ name: "Value", type: SignalType.ANALOG_INPUT },
],
},
]
/* old
// ============================================================
// Properties pro ComponentType
// ============================================================
const componentProperties: Record<ComponentTypes, PropertyDefinition[]> = {
[ComponentTypes.UNKNOWN]: [],
[ComponentTypes.ANTRIEB]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "power",
label: "Leistung [kW]",
type: "number",
},
],
[ComponentTypes.FREQUENZUMRICHTER]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "power",
label: "Leistung [kW]",
type: "number",
},
{
name: "maxFrequency",
label: "Max. Frequenz [Hz]",
type: "number",
},
],
[ComponentTypes.REPARATUR_SCHALTER]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "voltage",
label: "Spannung [V]",
type: "number",
},
],
[ComponentTypes.LIMIT_SWITCH]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "normallyClosed",
label: "Öffner",
type: "boolean",
},
],
[ComponentTypes.SCHIEFLAUF]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "sensitivity",
label: "Empfindlichkeit",
type: "number",
},
],
[ComponentTypes.REISSLEINE]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "length",
label: "Seillänge [m]",
type: "number",
},
],
[ComponentTypes.DREHZALHL]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "maxRpm",
label: "Max. Drehzahl [rpm]",
type: "number",
},
],
[ComponentTypes.VORORT]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "buttonCount",
label: "Anzahl Taster",
type: "number",
},
],
[ComponentTypes.UEBERWACHUNG]: [
{
name: "manufacturer",
label: "Hersteller",
type: "text",
},
{
name: "monitoringType",
label: "Überwachungstyp",
type: "select",
options: [
{
value: "temperature",
label: "Temperatur",
},
{
value: "pressure",
label: "Druck",
},
{
value: "vibration",
label: "Vibration",
},
],
},
],
}
*/
+134
View File
@@ -0,0 +1,134 @@
'use server'
import { Sites } from "@/_config/sites";
import { ComponentCreate, ComponentOfUnit, ComponentResponse, ComponentUpdate } from "@/types/anlagen/components";
import { UnitResponse } from "@/types/anlagen/units";
import { revalidatePath } from "next/cache";
const url = "http://localhost:3000/rk2/";
const url_component = url + "components"
//-----------------------------------------------------------------------------
//--- Component by ID
//-----------------------------------------------------------------------------
export async function api_component_byId(id: string): Promise<ActionResult<ComponentOfUnit>> {
const result = await fetch(`${url_component}/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
return { success: true, data:resultData as ComponentOfUnit };
}
//-----------------------------------------------------------------------------
//--- Component CREATE
//-----------------------------------------------------------------------------
export async function api_component_create(unitId:string,data: ComponentCreate): Promise<ActionResult<ComponentResponse>> {
console.log(data);
const result = await fetch(`${url_component}/${unitId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.units.url)
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Component UPDATE
//-----------------------------------------------------------------------------
export async function api_component_update(id:string,data: ComponentUpdate): Promise<ActionResult<UnitResponse>> {
console.log('Update unit' + id);
const result = await fetch(`${url_component}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.units.url + "/")
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Component Delete
//-----------------------------------------------------------------------------
export async function api_component_remove(compId: string): Promise<ActionResult<any>> {
const result = await fetch(`${url_component}/${compId}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.units.url);
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Component Delete
//-----------------------------------------------------------------------------
export async function api_componentPropertieDelete(compId: string,propName:string): Promise<ActionResult<any>> {
const result = await fetch(`url_component/${compId}/delteProp/${propName}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.units.url);
return { success: true, data:resultData };
}
+119
View File
@@ -0,0 +1,119 @@
'use server'
import { Sites } from "@/_config/sites";
import { GroupCreate, GroupResponse } from "@/types/anlagen/groups";
import { revalidatePath } from "next/cache";
const url = "http://localhost:3000/rk2/";
const url_groups = url + "groups"
//-----------------------------------------------------------------------------
//--- Groups
//-----------------------------------------------------------------------------
export async function api_groups(): Promise<GroupResponse[]> {
const result = await fetch(`${url_groups}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//-----------------------------------------------------------------------------
//--- Group by ID
//-----------------------------------------------------------------------------
export async function api_groups_byId(id: string): Promise<ActionResult<GroupResponse>> {
const result = await fetch(`${url_groups}/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Groups CREATE
//-----------------------------------------------------------------------------
export async function api_groups_create(data: GroupCreate): Promise<ActionResult<GroupResponse>> {
console.log('Create group');
const result = await fetch(`${url_groups}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.groups.url + "/")
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Groups UPDATE
//-----------------------------------------------------------------------------
export async function api_groups_update(id:string,data: GroupCreate): Promise<ActionResult<GroupResponse>> {
console.log('Update group' + id);
const result = await fetch(`${url_groups}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.groups.url)
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Groups DELETE
//-----------------------------------------------------------------------------
export async function api_groups_delete(id:string): Promise<ActionResult<GroupResponse>> {
console.log('Update group' + id);
const result = await fetch(`${url_groups}/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.groups.url)
return { success: true, data:resultData };
}
+97
View File
@@ -0,0 +1,97 @@
'use server'
import { Sites } from "@/_config/sites";
import { IoCreate, IoResponse } from "@/types/anlagen/ios";
import { revalidatePath } from "next/cache";
const url = "http://localhost:3000/rk2/";
const url_ios = url + "ios"
//-----------------------------------------------------------------------------
//--- ios
//-----------------------------------------------------------------------------
export async function api_ios(): Promise<IoResponse[]> {
const result = await fetch(`${url_ios}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//-----------------------------------------------------------------------------
//--- io by ID
//-----------------------------------------------------------------------------
export async function api_ios_byId(id: string): Promise<ActionResult<IoResponse>> {
const result = await fetch(`${url_ios}/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- ios CREATE
//-----------------------------------------------------------------------------
export async function api_ios_create(data: IoResponse): Promise<ActionResult<IoResponse>> {
const result = await fetch(`${url_ios}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.ios.url + "/")
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- ios UPDATE
//-----------------------------------------------------------------------------
export async function api_ios_update(id:string,data: IoCreate): Promise<ActionResult<IoResponse>> {
const result = await fetch(`${url_ios}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.ios.url)
return { success: true, data:resultData };
}
+112
View File
@@ -0,0 +1,112 @@
'use server'
import { Sites } from "@/_config/sites";
import { SignalCreate, SignalResponse } from "@/types/anlagen/signals";
import { revalidatePath } from "next/cache";
const url = "http://localhost:3000/rk2/";
const url_signals = url + "signals"
//-----------------------------------------------------------------------------
//--- Signals
//-----------------------------------------------------------------------------
export async function api_signals(): Promise<SignalResponse[]> {
const result = await fetch(`${url_signals}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//-----------------------------------------------------------------------------
//--- Signal by ID
//-----------------------------------------------------------------------------
export async function api_signals_byId(id: string): Promise<ActionResult<any>> {
const result = await fetch(`${url_signals}/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Signals by compId
//-----------------------------------------------------------------------------
export async function api_signalsByComp(compId:string): Promise<SignalResponse[]> {
const result = await fetch(`${url_signals}/byComponent/${compId}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//-----------------------------------------------------------------------------
//--- Signals CREATE
//-----------------------------------------------------------------------------
export async function api_signals_create(data: SignalCreate): Promise<ActionResult<any>> {
const result = await fetch(`${url_signals}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.signals.url + "/")
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Signals UPDATE
//-----------------------------------------------------------------------------
export async function api_signals_update(id:string,data: SignalCreate): Promise<ActionResult<any>> {
const result = await fetch(`${url_signals}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.signals.url)
return { success: true, data:resultData };
}
+123
View File
@@ -0,0 +1,123 @@
'use server'
import { Sites } from "@/_config/sites";
import { ComponentCreate, ComponentUpdate } from "@/types/anlagen/components";
import { UnitCreate, UnitResponse } from "@/types/anlagen/units";
import { revalidatePath } from "next/cache";
const url = "http://localhost:3000/rk2/";
const url_units = url + "units"
const comp_update = "component"
const comp_add = "addComponent"
//-----------------------------------------------------------------------------
//--- Units
//-----------------------------------------------------------------------------
export async function api_units(): Promise<UnitResponse[]> {
const result = await fetch(`${url_units}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await result.json();
return data;
}
//-----------------------------------------------------------------------------
//--- Units by ID
//-----------------------------------------------------------------------------
export async function api_units_byId(id: string): Promise<ActionResult<UnitResponse>> {
const result = await fetch(`${url_units}/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Units CREATE
//-----------------------------------------------------------------------------
export async function api_units_create(data: UnitCreate): Promise<ActionResult<UnitResponse>> {
const result = await fetch(`${url_units}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.units.url + "/")
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Units UPDATE
//-----------------------------------------------------------------------------
export async function api_units_update(id:string,data: UnitCreate): Promise<ActionResult<UnitResponse>> {
console.log('Update unit');
console.log(data);
const result = await fetch(`${url_units}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.units.url + "/")
return { success: true, data:resultData };
}
//-----------------------------------------------------------------------------
//--- Units UPDATE Component
//-----------------------------------------------------------------------------
export async function api_units_updateComponent(unitId: string, compId: string, data: ComponentUpdate): Promise<ActionResult<UnitResponse>> {
console.log('Create unit' );
const result = await fetch(`${url_units}${unitId}/${comp_update}/${compId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const resultData = await result.json();
if (!result.ok) {
return {
success: false,
errors: resultData.message, // kommt aus NestJS
};
}
revalidatePath(Sites.rk2.units.url + "/")
return { success: true, data:resultData };
}
+105
View File
@@ -0,0 +1,105 @@
export default async function PageElements() {
return (
<div className="p-4">
<section>
<div className="font-bold">Gruppe</div>
<ul>
<li>G35</li>
<li>Name:Gurtförderr</li>
<li>Typ:Band</li>
<section className="ps-8">
<div className="font-bold">Elemente</div>
<ul className="ps-8">
<li className="flex gap-4">
<div className="w-15 font-bold">Name:</div>
<div className="">Motor</div>
</li>
<li>Seilzug</li>
<li>Repschalter</li>
<li>Schieflauf</li>
<li>Vorortbedienung</li>
<li>Endschalter</li>
<li>FU</li>
<li>Drehzahl</li>
</ul>
</section>
</ul>
</section>
</div>
);
}
/*
(Group)BA3
(Group)H-Förderer
(Group)G35 Band
(Element) Motor 1
(Element) Motor 2
(Element) Drehzahl vorne
(Element) Reperaturschalter
(Element) Reissleine
(Element) Endschalter
(Element) Visu
*/
interface Group {
id: number,
name: string,
nr: string,
container: boolean
groups?: Group[]
}
interface Unit {
group: Group
typ: string,
name: string,
componentes: Componente[]
}
type Componente = ComponenteMotor | ComponenteSchieflauf;
interface ComponenteBase {
name: string,
typ: string,
}
interface ComponenteMotor extends ComponenteBase {
motorEigenschaften: string //nur Platzhalter momentan
}
interface ComponenteSchieflauf {
name: string,
trueOK:boolean
}
const beispielBand: Unit = {
group: {
id: 1,
name: "BA4",
nr: '100',
container: true
},
typ: "Band",
name: "Förderband zu 1234",
componentes: [{
name: "Antrieb",
typ: "Motor",
motorEigenschaften: "platzhalter Beispiel"
},
{
name: "Schieflauf",
typ: "Schieflauf",
trueOK: true
},
]
}
@@ -0,0 +1,28 @@
"use client";
import Modal from "@/components/ui/Modal";
import { useRouter } from "next/navigation";
interface Props{
test:string
}
export default function AggregateDetails({test}:Props) {
const router = useRouter();
return (
<Modal goBack={true}>
<h2>Produktdetails</h2>
<p>Hier stehen die detaillierten Informationen.</p>
<button onClick={() => router.back()}>
Schließen
</button>
</Modal>
);
}
@@ -0,0 +1,4 @@
export default function Default() {
return null
}
@@ -0,0 +1,18 @@
export default async function DetailsPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return (
<main>
<h1>Details für Produkt {id}</h1>
<p>
Diese Seite wird angezeigt, wenn man direkt
/produkte/{id}/details aufruft.
</p>
</main>
);
}
@@ -0,0 +1,14 @@
export default function Layout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<>
{children}
{modal}
</>
);
}
@@ -0,0 +1,74 @@
import { Sites } from "@/_config/sites";
import { api_ProconAggregate_GetById } from "@/actions/procon/api-procon-aggregate";
import Visualisierungen from "@/components/procon/Visualsierungen";
import Link from "next/link";
type Props = {
params: Promise<{
id: string;
}>;
};
export default async function PageAggregateDetail({ params }: Props) {
const { id } = await params;
const data = await api_ProconAggregate_GetById(Number(id));
return (
<div className=" ">
{/* Überschrift Leiste */}
<div className="p-4 border-1 m-1 flex gap-4 mb-6">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
<Link href={Sites.procon.aggregate.url}>{Sites.procon.aggregate.name}</Link>
</div>
{/* Aggregat Base Data */}
<div>
<div className="ps-4 flex gap-4">
<div className="font-bold">Anlage :</div>
<div>{data.anlage}</div>
</div>
<div className="ps-8 flex gap-4">
<div className="font-bold w-30">Agg Nr :</div>
<div>{data.aggNr}</div>
</div>
<div className="ps-8 flex gap-4">
<div className="font-bold w-30">Bezeichnung :</div>
<div>{data.bezeichnung}</div>
</div>
<div className="ps-8 flex gap-4">
<div className="font-bold w-30">SPS Name :</div>
<div>{data.viewName}</div>
</div>
<div className="ps-8 flex gap-4">
<div className="font-bold w-30">BMK :</div>
<div>{data.bmk}</div>
</div>
<div>ss <Link href={`/procon/aggregate/${id}/details`}>Modal</Link></div>
</div>
{/* Visualisrung Elemente Anzeigen */}
<div className="p-4 m-4 border-1 gap-8 w-400 rounded-lg border-gray-200">
{data.visuElements ?
<Visualisierungen visuElements={data.visuElements} />:
<div>Keine Daten</div>
}
</div>
{false &&
<div className="flex flex-col m-4 w-300 ">
{JSON.stringify(data, null, 2)}
{id}
</div>
}
</div>
);
}
+40
View File
@@ -0,0 +1,40 @@
import { Sites } from "@/_config/sites";
import { api_ProconAggregate_GetAll } from "@/actions/procon/api-procon-aggregate";
import Link from "next/link";
export default async function PageAggregate() {
const aggregate = await api_ProconAggregate_GetAll();
return (
<div className="">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
<Link href={Sites.procon.url}>{Sites.procon.name}</Link>
<div className="flex flex-col m-4 w-300 ">
{aggregate.map((anlage) => (
<div key={anlage.Id}
className="grid grid-cols-[50px_100px_200px_200px_200px_200px] gap-4 p-2 border-b-1 border-l-1 border-r-1 first:border-t-1">
<div>
{anlage.Id}
</div>
<div>
{anlage.SPSAntriebId}
</div>
<div>
{anlage.Bezeichnung}
</div>
<div>
{anlage.BMK}
</div>
<div>
{anlage.ViewName}
</div>
<div>
<Link href={Sites.procon.aggregate.url+"/"+anlage.SPSAntriebId}>Auswahl</Link>
</div>
</div>))}
</div>
</div>
);
}
+30
View File
@@ -0,0 +1,30 @@
import { Sites } from "@/_config/sites";
import { api_ProconAggregate_GetAll } from "@/actions/procon/api-procon-aggregate";
import Link from "next/link";
export default async function PageAggregate() {
const topics = [
{ name: Sites.procon.aggregate.name, url: Sites.procon.aggregate.url },
{ name: Sites.procon.texteVisu.name, url: Sites.procon.texteVisu.url },
{ name: "Visu Texte",url: Sites.procon.texteVisu.url },
{ name: "SVG Test",url: Sites.procon.svgTest.url }
]
return (
<div className="">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
<div className="flex flex-col m-4 w-300 ">
{topics.map((top) => (
<Link href={top.url} key={top.name+top.url}>
{top.name}
</Link>
))}
</div>
</div >
);
}
+52
View File
@@ -0,0 +1,52 @@
"use client"
import { Sites } from "@/_config/sites";
import { api_ProconAggregate_GetAll } from "@/actions/procon/api-procon-aggregate";
import SvgView from "@/components/procon/svg/SvgView";
import { xamlToSvg } from "@/components/procon/svg/xaml_to_svg";
import Link from "next/link";
import { useEffect, useState } from "react";
const x = `<?xml version="1.0" ?>
<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Canvas Width="24" Height="24">
<Canvas>
<Ellipse Canvas.Left="0" Canvas.Top="0" Width="24" Height="24" StrokeThickness="1" Stroke="#000000" Fill="#808080" ></Ellipse>
<TextBlock Canvas.Left="5"
Canvas.Top="0"
Foreground="#000000"
Text="M"
FontSize="16"
FontWeight="Bold"/>
</Canvas>
</Canvas>
</Viewbox>
`
export default function PageSVG() {
const [xaml, setXaml] = useState(x);
const getContent = () => {
try {
return <SvgView svg={xamlToSvg(xaml, { pretty: true })}></SvgView>;
} catch (error) {
console.log('Unbekannter fehler')
}
};
return (
<div className="">
<textarea className="w-300" name="svg" id="1" value={xaml} onChange={(e) => setXaml(() => e.target.value)} >
</textarea>
{
getContent()
}
</div>
);
}
+23
View File
@@ -0,0 +1,23 @@
import { Sites } from "@/_config/sites";
import { api_anlage_getSelected, api_anlage_setSelected, api_anlagen_getAll } from "@/actions/rk/api-anlage";
import AnlageColumn from "@/components/rk/anlage/anlageColumn";
import Link from "next/link";
export default async function PageAnlagen() {
const anlagen = await api_anlagen_getAll();
const currentAnlage = await api_anlage_getSelected();
return (
<div className="">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
<div className="flex flex-col m-4">
{anlagen.map((anlage) =>
<AnlageColumn key={anlage.id} data={anlage} isSelected={anlage.id === currentAnlage.id} />
)}
</div>
</div>
)
}
+29
View File
@@ -0,0 +1,29 @@
import { Sites } from "@/_config/sites";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { api_ElementsGet } from "@/actions/rk/api-element";
import { api_GroupAll } from "@/actions/rk/api-groups";
import ElementCreateView from "@/components/rk/elements/elementCreateView";
import Link from "next/link";
export default async function PageElementNew() {
const groups = await api_GroupAll();
const currentAnlage = await api_anlage_getSelected()
const elements = await api_ElementsGet();
return (
<div className="">
<div className="border-1 flex gap-4 p-4">
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.elements.url}>{Sites.elements.name}</Link>
</div>
<div>
<Link href={Sites.main.url}>{Sites.main.name}</Link>
</div>
</div>
<ElementCreateView analge={currentAnlage} elements={elements} groups={groups} />
</div>
);
}
+29
View File
@@ -0,0 +1,29 @@
import { Sites } from "@/_config/sites";
import { api_ElementsGet } from "@/actions/rk/api-element";
import { api_GroupAll } from "@/actions/rk/api-groups";
import ElementView from "@/components/rk/elements/elementsView";
import Link from "next/link";
export default async function PageElements() {
const elements = await api_ElementsGet();
const groups = await api_GroupAll();
return (
<div className="">
<div className="border-1 flex gap-4 p-4">
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
</div>
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.elements.new.url}>{Sites.elements.new.name}</Link>
</div>
</div>
<ElementView elements={elements} groups={groups}></ElementView>
</div>
);
}
+28
View File
@@ -0,0 +1,28 @@
import { Sites } from "@/_config/sites";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { api_GroupAll } from "@/actions/rk/api-groups";
import { GroupEdit } from "@/components/rk/groups/group_Edit";
import { GroupEditList } from "@/components/rk/groups/group_edit_list";
import GroupView from "@/components/rk/groups/groupView";
import Link from "next/link";
export default async function PageGroupNew() {
const groups = await api_GroupAll();
const anlage = await api_anlage_getSelected()
return (
<div className="">
<div className="border-1 flex gap-4 p-4">
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.groups.url}>{Sites.groups.name}</Link>
</div>
</div>
<Link href={Sites.main.url}>{Sites.main.name}</Link>
{/*<GroupEdit anlage={anlage} debug={true}></GroupEdit>*/}
<GroupEditList data={groups} />
</div>
);
}
+26
View File
@@ -0,0 +1,26 @@
import { Sites } from "@/_config/sites";
import { api_GroupAll } from "@/actions/rk/api-groups";
import GroupView from "@/components/rk/groups/groupView";
import Link from "next/link";
export default async function PageGroups() {
const groups = await api_GroupAll();
return (
<div className="">
<div className="border-1 flex gap-4 p-4">
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
</div>
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.groups.new.url}>{Sites.groups.new.name}</Link>
</div>
</div>
<GroupView groups={groups}></GroupView>
</div>
);
}
+28
View File
@@ -0,0 +1,28 @@
import { Sites } from "@/_config/sites";
import { api_GroupAll } from "@/actions/rk/api-groups";
import { api_LogNoteGet } from "@/actions/rk/api-logNote";
import GroupView from "@/components/rk/groups/groupView";
import LogNoteView from "@/components/rk/logNote/logNoteView";
import Link from "next/link";
export default async function PageGroups() {
const notes = await api_LogNoteGet();
return (
<div className="">
<div className="border-1 flex gap-4 p-4">
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
</div>
<div className="rounded-lg bg-gray-100 p-2">
<Link href={Sites.logNote.new.url}>{Sites.logNote.new.name}</Link>
</div>
</div>
<LogNoteView notes={notes}></LogNoteView>
</div>
);
}
@@ -0,0 +1,46 @@
import { Sites } from "@/_config/sites";
import { PeripherieFindApi, GetEA_Differences } from "@/actions/rk/api-peripherie";
import Link from "next/link";
export default async function PagePeripherie() {
const peripheries = await GetEA_Differences();
return (
<div className="">
<div className="flex justify-center font-bold ">
<div className="text-[24px]">
E/A Unterschiede
</div>
</div>
<div className="flex gap-4 p-2 p-4 border-1">
<div className="bg-sky-200 p-2 rounded-md">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
</div>
<div className="bg-sky-200 p-2 rounded-md">
<Link href={Sites.peripheries.url}>{Sites.peripheries.name}</Link>
</div>
</div>
{peripheries.map((p) => (<div key={p.id} className="grid grid-cols-[100px_600px_600px] mt-4 ms-4">
<div>
{p.adrType}{p.adrByte}.{p.adrBit}
</div>
<div>
{p.text}
</div>
<div>
{p.excelData?.functionText}
</div>
</div>))}
</div>
);
}
+37
View File
@@ -0,0 +1,37 @@
import { Sites } from "@/_config/sites";
import { api_ElementsGet } from "@/actions/rk/api-element";
import { api_GroupAll } from "@/actions/rk/api-groups";
import { PeripherieFindApi } from "@/actions/rk/api-peripherie";
import PeripherieListView from "@/components/rk/peripherie/peripherieListView";
import Link from "next/link";
export default async function PagePeripherie() {
const peripheries = await PeripherieFindApi(); //{ groupId: "exists" }
const groups = await api_GroupAll();
const elmente = await api_ElementsGet();
return (
<div className="">
<div className="border-1 m-4 flex gap-4 p-4">
<div className="bg-gray-100 p-2 rounded-lg">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
</div>
<div className="bg-gray-100 p-2 rounded-lg">
<Link href={Sites.peripheries.differences.url}>{Sites.peripheries.differences.name}</Link>
</div>
</div>
<PeripherieListView peripheries={peripheries} groups={groups} elemente={elmente} />
</div>
);
}
@@ -0,0 +1,50 @@
import { Sites } from "@/_config/sites";
import { api_units_byId } from "@/actions/rk2/api-units";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { UnitCreateForm } from "@/components/rk2/units/unit_createForm";
import Link from "next/link";
import { UnitEdit } from "@/components/rk2/units/unitEdit";
import { api_groups, api_groups_byId } from "@/actions/rk2/api-groups";
import { componentDefaults, componentTypeDefinitions } from "@/actions/rk2/api-compTypes";
import Breadcrumbs from "@/components/ui/navigation/breadcrumbs";
import { GroupForm } from "@/components/rk2/groups/groupForm";
import { GroupEdit } from "@/components/rk2/groups/groupEdit";
type Props = {
params: Promise<{
id: string;
}>;
};
export default async function PageGroupEdit({ params }: Props) {
const { id } = await params;
const resultFindGroupById = await api_groups_byId(id);
const anlage = await api_anlage_getSelected();
const group = resultFindGroupById.data;
const groups = await api_groups();
const breadcrumb = [{ label: "RK", href: "/" },{ label: "Anlage", href: "/rk2" },{ label: "Groups", href: "/rk2/groups" }, { label: "Edit" }, { label: group.name }]
if (!resultFindGroupById.success) return <div>Grouppe nicht gefunden {resultFindGroupById.errors} {id}</div>
return (
<div className="">
<div>
<Breadcrumbs items={breadcrumb}/>
</div>
<div className="flex flex-col m-4 w-300 ">
<GroupEdit anlage={anlage} selectedGroup={group} groups={groups} />
</div>
</div>
);
}
@@ -0,0 +1,30 @@
import { Sites } from "@/_config/sites";
import { api_groups } from "@/actions/rk2/api-groups";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { GroupEdit } from "@/components/rk2/groups/groupEdit";
import Link from "next/link";
import Breadcrumbs from "@/components/ui/navigation/breadcrumbs";
export default async function PageGroups() {
const currentAnlage= await api_anlage_getSelected();
const groups = await api_groups();
const breadcrumb = [{ label: "RK", href: "/" },{ label: "Anlage", href: "/rk2" },{ label: "Groups", href: "/rk2/groups" }, { label: "New" }]
return (
<div className="">
<div>
<Breadcrumbs items={breadcrumb}/>
</div>
<div className="flex flex-col m-4 w-300 ">
<GroupEdit anlage={currentAnlage} groups={groups} />
</div>
</div>
);
}
+29
View File
@@ -0,0 +1,29 @@
import { Sites } from "@/_config/sites";
import { api_groups } from "@/actions/rk2/api-groups";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { GroupViewList } from "@/components/rk2/groups/groupViewList";
import Link from "next/link";
import Breadcrumbs from "@/components/ui/navigation/breadcrumbs";
export default async function PageGroups() {
const currentAnlage= await api_anlage_getSelected();
const groups = await api_groups();
const breadcrumb = [{ label: "RK", href: "/" },{ label: "Anlage", href: "/rk2" },{ label: "Groups" }]
return (
<div className="">
<div>
<Breadcrumbs items={breadcrumb}/>
</div>
<div className="flex flex-col m-4 w-300 ">
<GroupViewList anlage={currentAnlage} groups={groups} />
</div>
</div>
);
}
+55
View File
@@ -0,0 +1,55 @@
"use client"
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { Anlage } from "@/types/rk";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
interface Props {
children: React.ReactNode
}
export default function AnlageLayout({ children }: Props) {
const router = useRouter();
const [anlage,setAnlage] = useState<Anlage>()
useEffect(()=>{
const loadAnlage = async ()=>{
const currentAnlage = await api_anlage_getSelected();
setAnlage(()=>currentAnlage);
}
loadAnlage();
},[])
return (
<div className="flex h-screen flex-col overflow-hidden">
<header className="flex h-16 items-center border-b bg-blue-600 text-white px-6 ">
<h1 className="text-xl font-semibold ">
{`RK -${anlage?.anlageNr} ${anlage?.name}`}
</h1>
</header>
<div className="flex min-h-0 flex-1">
<aside className="w-64 border-r bg-gray-50 p-4">
<nav className="flex flex-col gap-2">
<h2 className="mb-4 font-semibold">Anlage</h2>
<ul className="space-y-2">
<li>
<a href="/rk2/groups">Groups</a>
</li>
<li>
<a href="/rk2/units">Units</a>
</li>
</ul>
</nav>
</aside>
{/* Content */}
<main className="min-w-0 flex-1 overflow-y-auto p-6">
{children}
</main>
</div>
</div>
);
}
+51
View File
@@ -0,0 +1,51 @@
import { Sites } from "@/_config/sites";
import { api_units } from "@/actions/rk2/api-units";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import Link from "next/link";
import { List, ListItem, ListItemAction } from "@/components/ui/list";
import { Button } from "@/components/ui/button";
import Breadcrumbs from "@/components/ui/navigation/breadcrumbs";
export default async function PageUnits() {
const currentAnlage = await api_anlage_getSelected();
const units = await api_units();
const breadcrumb = [{ label: "RK", href: "/" },{ label: "Anlage" }]
return (
<>
{/* Main-Nav */}
<Breadcrumbs items={breadcrumb}/>
<div className="flex flex-col m-4 w-170 ">
<List variant={"transparent"} >
{units.map((unit) => (
<ListItem hover={true} key={unit.id}>
<div className="w-50">
{unit.name}
</div>
<div className="w-20">
{unit.type}
</div>
<div className="w-40">
{unit.components?.map((comp) => <div key={comp.id}><div>{comp.name}</div></div>)}
</div>
<ListItemAction>
<Button variant={"secondary"} size={"xs"} >
<Link href={`/rk2/units/edit/${unit.id}`}>Auswahl</Link>
</Button>
</ListItemAction>
</ListItem>
))}
</List>
</div>
</>
);
}
@@ -0,0 +1,47 @@
import { Sites } from "@/_config/sites";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { api_component_byId } from "@/actions/rk2/api-component";
import { componentTypeDefinitions } from "@/actions/rk2/api-compTypes";
import { SignalForm } from "@/components/rk2/signals/signalForm";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Link } from "lucide-react";
type Props = {
params: Promise<{
componentId: string;
}>;
};
export default async function PageSignalsEdit({ params }: Props) {
const { componentId } = await params;
const resultComponentOfId = await api_component_byId(componentId);
const currentAnlage= await api_anlage_getSelected();
const componentTypes = componentTypeDefinitions;
if(!resultComponentOfId) return <div>Keine Daten</div>
return (
<div className="">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
<Link href={Sites.procon.url}>{Sites.procon.name}</Link>
<div className="flex flex-col m-4 w-300 ">
<SignalForm componente={resultComponentOfId.data.component} ></SignalForm>
{true &&
<Card positons={"start"}>
<CardHeader variant={"danger"} >Debug</CardHeader>
<CardContent className="ps-8">
{componentId}
<pre>{JSON.stringify(resultComponentOfId, null, 2)}</pre>
</CardContent>
</Card>
}
</div>
</div>
);
}
@@ -0,0 +1,46 @@
import { Sites } from "@/_config/sites";
import { api_units_byId } from "@/actions/rk2/api-units";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { UnitCreateForm } from "@/components/rk2/units/unit_createForm";
import Link from "next/link";
import { UnitEdit } from "@/components/rk2/units/unitEdit";
import { api_groups } from "@/actions/rk2/api-groups";
import { componentDefaults, componentTypeDefinitions } from "@/actions/rk2/api-compTypes";
import Breadcrumbs from "@/components/ui/navigation/breadcrumbs";
type Props = {
params: Promise<{
id: string;
}>;
};
export default async function PageUnitsEdit({ params }: Props) {
const { id } = await params;
const resultFindUnitById = await api_units_byId(id);
const currentAnlage= await api_anlage_getSelected();
const groups = await api_groups();
const componentTypes = componentTypeDefinitions;
const cd = componentDefaults;
if(!resultFindUnitById.success) return <div>Unit nicht gefunden {resultFindUnitById.errors}</div>
const breadcrumb = [{ label: "RK", href: "/" },{ label: "Anlage", href: "/rk2" },{ label: "Units",href: "/rk2/units" },{ label: "Ändern" },{ label: resultFindUnitById.data.name }]
return (
<div className="">
<div>
<Breadcrumbs items={breadcrumb}/>
</div>
<div className="flex flex-col m-4 w-300 ">
<UnitEdit anlage={currentAnlage} groups={groups} selectedUnit={resultFindUnitById.data} componentTypes={componentTypes} componentDefaults={cd}></UnitEdit>
</div>
</div>
);
}
@@ -0,0 +1,34 @@
import { Sites } from "@/_config/sites";
import { api_groups } from "@/actions/rk2/api-groups";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { UnitEdit } from "@/components/rk2/units/unitEdit";
import Link from "next/link";
import { componentDefaults, componentTypeDefinitions } from "@/actions/rk2/api-compTypes";
import Breadcrumbs from "@/components/ui/navigation/breadcrumbs";
export default async function PageUnitsEditNew() {
const currentAnlage = await api_anlage_getSelected();
const groups = await api_groups();
const componentTypes = componentTypeDefinitions;
const cd = componentDefaults;
const breadcrumb = [{ label: "RK", href: "/" },{ label: "Anlage", href: "/rk2" },{ label: "Units" },{ label: "Neu" }]
return (
<div className="">
<div>
<Breadcrumbs items={breadcrumb}/>
</div>
<div className="flex flex-col m-4 w-300 ">
<UnitEdit anlage={currentAnlage} groups={groups} componentTypes={componentTypes} componentDefaults={cd}></UnitEdit>
{/*
<UnitCreateForm anlage={currentAnlage}></UnitCreateForm>
*/}
</div>
</div>
);
}
+83
View File
@@ -0,0 +1,83 @@
import { Sites } from "@/_config/sites";
import { api_units } from "@/actions/rk2/api-units";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import Link from "next/link";
import { List, ListHeader, ListHeaderItem, ListItem, ListItemAction, ListItemContent, ListItemDescription, ListItemTitle } from "@/components/ui/list";
import { Button } from "@/components/ui/button";
import Breadcrumbs from "@/components/ui/navigation/breadcrumbs";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { GroupPath } from "@/components/rk2/groups/groupPath";
import { api_groups } from "@/actions/rk2/api-groups";
import { AusgabeUnitInfo } from "@/components/rk2/ausgabe/ausgabeUnitInfo";
export default async function PageUnits() {
const units = await api_units();
const groups = await api_groups();
const breadcrumb = [{ label: "RK", href: "/" }, { label: "Anlage", href: "/rk2" }, { label: "Units" }]
return (
<>
{/* Main-Nav */}
<div>
<Breadcrumbs items={breadcrumb} />
</div>
<Card className="flex flex-col m-4 w-170 ">
<CardHeader>Units</CardHeader>
<Button size={"xs"} className="w-30 mt-2 ms-2 mb-2">
<Link href={Sites.rk2.units.new.url}>{Sites.rk2.units.new.name}</Link>
</Button>
<List variant={"transparent"} >
<ListHeader>
<ListHeaderItem className="w-50">Gruppe</ListHeaderItem>
<ListHeaderItem className="w-50">Unit</ListHeaderItem>
<ListHeaderItem>Typ</ListHeaderItem>
</ListHeader>
{units.map((unit) => (
<ListItem hover={true} key={unit.id}>
<ListItemContent >
<ListItemTitle className="flex">
<div className="w-50">
<GroupPath groups={groups} groupId={unit.groupId} />
</div>
<div className="w-50">
{unit.prefix}_{unit.bmk}_{unit.name}{unit.postfix ? "_"+unit.postfix:""}
</div>
<div className="w-20">
{unit.type}
</div>
</ListItemTitle>
<ListItemDescription className="ps-50 pt-1 flex gap-2">
{unit.components?.map((comp) => <div key={comp.id}>
{comp.prefix && comp.prefix}
{comp.name}
{comp.postfix && comp.postfix}
{comp.index && comp.index>0 ? comp.index:""}
</div>)}
</ListItemDescription>
</ListItemContent>
<ListItemAction>
<Button variant={"secondary"} size={"xs"} >
<Link href={`/rk2/units/edit/${unit.id}`}>Auswahl</Link>
</Button>
</ListItemAction>
</ListItem>
))}
</List>
</Card >
<Card>
<CardHeader>Ausgabe</CardHeader>
<CardContent>
<AusgabeUnitInfo units={units} groups={groups} />
</CardContent>
</Card>
</>
);
}
+142
View File
@@ -0,0 +1,142 @@
"use client";
import { DndContext, closestCenter, DragOverlay, useDroppable } from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useState } from "react";
import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core";
import { SortableItem } from "@/components/test_dnd";
import { DroppableContainer } from "@/components/ui/droppableContainer";
interface TestItem {
id: string,
name: string
}
export default function Page() {
const [left, setLeft] = useState<TestItem[]>([{ id: "1", name: "Apfel" }, { id: "2", name: "Banane" }]);
const [right, setRight] = useState<TestItem[]>([{ id: "3", name: "Traube" }]);
const [activeItem, setActiveItem] = useState<TestItem | null>(null);
function findContainer(id: string) {
if (left.find((item) => item.id === id)) return "left";
if (right.find((item) => item.id === id)) return "right";
return null;
}
function handleDragStart(event: DragStartEvent) {
const { active } = event;
const item =
left.find((i) => i.id === active.id) ||
right.find((i) => i.id === active.id);
if (item) setActiveItem(item);
}
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
setActiveItem(null);
if (!over) return;
const overId = over.id;
const activeContainer = findContainer(active.id as string);
const overContainer =
overId === "left" || overId === "right"
? overId
: findContainer(overId as string);
if (!activeContainer || !overContainer) return;
// 🔁 Innerhalb gleicher Liste sortieren
if (activeContainer === overContainer) {
if (activeContainer === "left") {
const oldIndex = left.findIndex((i) => i.id === active.id);
const newIndex = left.findIndex((i) => i.id === over.id);
setLeft(arrayMove(left, oldIndex, newIndex));
}
if (activeContainer === "right") {
const oldIndex = right.findIndex((i) => i.id === active.id);
const newIndex = right.findIndex((i) => i.id === over.id);
setRight(arrayMove(right, oldIndex, newIndex));
}
} else {
if (activeContainer === "left") {
const item = left.find((i) => i.id === active.id);
if (!item) return;
setLeft(left.filter((i) => i.id !== active.id));
setRight([...right, item]);
}
if (activeContainer === "right") {
const item = right.find((i) => i.id === active.id);
if (!item) return;
setRight(right.filter((i) => i.id !== active.id));
setLeft([...left, item]);
}
}
}
return (
<div style={{ padding: 40 }}>
<DndContext collisionDetection={closestCenter} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<div className="flex gap-10 border-1">
<DroppableContainer id="left">
<SortableContext items={left.map(i => i.id)} strategy={verticalListSortingStrategy}>
<div className="border-1 p-4">
<h3>Links</h3>
{left.map((item) => (
<SortableItem key={item.id} item={item} ><div>Test</div></SortableItem>
))}
</div>
</SortableContext>
</DroppableContainer>
<DroppableContainer id="right">
<SortableContext items={right.map(i => i.id)} strategy={verticalListSortingStrategy}>
<div className="border-1 p-4">
<h3>Rechts</h3>
{right.map((item) => (
<SortableItem key={item.id} item={item} ><div>Test</div></SortableItem>
))}
</div>
</SortableContext>
</DroppableContainer>
</div>
{/* DRAG OVERLAY */}
<DragOverlay>
{activeItem ? (
<div
style={{
padding: 12,
border: "1px solid black",
background: "lightyellow",
}}
>
{activeItem.id} {activeItem.name}
</div>
) : null}
</DragOverlay>
</DndContext>
</div>
);
}
+10
View File
@@ -0,0 +1,10 @@
import { uploadFile } from "@/actions/rk/api-file-uplaod";
export default function UploadPage() {
return (
<form action={uploadFile}>
<input type="file" name="file" />
<button type="submit">Upload</button>
</form>
);
}
+16
View File
@@ -0,0 +1,16 @@
import { drawGetSvg } from "@/actions/rk/api-draw";
import { api_VisuElementsAll } from "@/actions/rk/api-visuElements";
import VisuCreatePlatform from "@/components/visu/create/visuCreatePlatform";
import VisuContent from "@/components/visu/visuContent";
export default async function PageVisu() {
return (
<div className="">
<VisuCreatePlatform />
</div>
);
}
+21
View File
@@ -0,0 +1,21 @@
import { api_ElementsGet } from "@/actions/rk/api-element";
import { api_VisuElementsAll } from "@/actions/rk/api-visuElements";
import VisuElementsList from "@/components/visu/visuElements/visuElementsList";
export default async function PageVisuElemente() {
const visuElements = await api_VisuElementsAll();
const elements = await api_ElementsGet();
if(!visuElements) {
return <div>Loading...</div>
}
return (
<div className="">
<VisuElementsList visuElements={visuElements} elements={elements}></VisuElementsList>
</div>
);
}
+28
View File
@@ -0,0 +1,28 @@
import { Sites } from "@/_config/sites";
import { api_ElementsGet } from "@/actions/rk/api-element";
import { api_GroupAll } from "@/actions/rk/api-groups";
import { api_VisuElementsAll } from "@/actions/rk/api-visuElements";
import VisuAddGroups from "@/components/visu/visuAddGroups";
import Link from "next/link";
export default async function PageGroups() {
const groups = await api_GroupAll();
const elements = await api_ElementsGet();
const visuElements = await api_VisuElementsAll();
if(!groups || !visuElements) return <div>Loading...</div>
return (
<div className="">
<Link href={Sites.main.url}>{Sites.main.name}</Link>
<div>
<VisuAddGroups groups={groups} elements={elements} visuElements={visuElements}></VisuAddGroups>
</div>
</div>
);
}
+22
View File
@@ -0,0 +1,22 @@
import { VisuElementsProvider } from "@/_context/visu/VisuElementsProvider";
import { drawGetSvg } from "@/actions/rk/api-draw";
import { api_VisuElementsAll } from "@/actions/rk/api-visuElements";
import VisuContent from "@/components/visu/visuContent";
export default async function PageVisu() {
const visuElements = await api_VisuElementsAll();
console.log('reander')
if(!visuElements) {
console.log('reload!!!')
return <div>Loading...</div>
}
return (
<div className="">
<VisuElementsProvider initialValues={visuElements}>
<VisuContent />
</VisuElementsProvider>
</div>
);
}
+39
View File
@@ -0,0 +1,39 @@
import { VisuElementsProvider } from "@/_context/visu/VisuElementsProvider";
import { api_drawData } from "@/actions/draw/api-draw";
import { drawGetSvg } from "@/actions/rk/api-draw";
import { api_VisuElementsAll } from "@/actions/rk/api-visuElements";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { SvgShow } from "@/components/visu/draw/svgShow";
import VisuContent from "@/components/visu/visuContent";
export default async function PageShowDraw() {
const data = await api_drawData();
return (
<div className="bg-gray-200">
<svg
width="180%"
height="180%"
viewBox={`${0} ${0} ${1820} ${1120}`}
>
<rect x1={1} y1={0} width={1750} height={1500} fill="#fee0fe" stroke="#000" strokeWidth={1} />
<SvgShow svg={data.svg} />
</svg>
{true &&
<Card positons={"start"}>
<CardHeader variant={"danger"} >Debug</CardHeader>
<CardContent className="ps-8">
<pre>{JSON.stringify(data, null, 2)}</pre>
</CardContent>
</Card>
}
</div>
);
}
+3 -4
View File
@@ -23,10 +23,9 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="de">
<body <body className={`${geistSans.variable} ${geistMono.variable} antialiased`} >
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children} {children}
</body> </body>
</html> </html>
+26 -58
View File
@@ -1,65 +1,33 @@
import Image from "next/image"; import { Sites } from "@/_config/sites";
import Image from "next/image";
import Link from "next/link";
export default async function Home() {
export default function Home() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black"> <div className="flex min-h-screen bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image <div className="flex flex-col m-4">
className="dark:invert" <Link href={Sites.anlagen.url}>{Sites.anlagen.name}</Link>
src="/next.svg" <Link href={Sites.groups.url}>{Sites.groups.name}</Link>
alt="Next.js logo" <Link href={Sites.elements.url}>{Sites.elements.name}</Link>
width={100} <Link href={Sites.peripheries.url}>{Sites.peripheries.name}</Link>
height={20} <Link href={Sites.visu.url}>{Sites.visu.name}</Link>
priority <Link href={Sites.visu.create.url}>{Sites.visu.create.name}</Link>
/> <Link href={Sites.visu.selectGroups.url}>{Sites.visu.selectGroups.name}</Link>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left"> <Link href={Sites.visu.elements.url}>{Sites.visu.elements.name}</Link>
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50"> <Link href={Sites.logNote.url}>{Sites.logNote.name}</Link>
To get started, edit the page.tsx file.
</h1> <Link href={Sites.logNote.url}>{Sites.logNote.name}</Link>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div> </div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a <div className="flex flex-col m-4">
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" <Link href={Sites.rk2.url}>{Sites.rk2.name}</Link>
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div> </div>
</main>
</div> </div>
); );
} }
+119
View File
@@ -0,0 +1,119 @@
"use client"
import { ProConView, ProConXamlData } from "@/types/procon"
import { useEffect, useState } from "react";
import VisualisierungPositionen from "./visualiserungPositionen";
import { api_ProconViews_UpdateXAML } from "@/actions/procon/api-procon-views";
import { useVisuAgg } from "@/hooks/procon/useVisuAgg";
import SvgView from "./svg/SvgView";
interface Props {
visuElements: ProConView[]
}
export default function Visualisierungen({ visuElements }: Props) {
const [selectedElement, setSelectedElement] = useState<ProConView | undefined>();
const [selectedView, setSelectedView] = useState<ProConXamlData | undefined>();
const { elements, viewNames, selectedViewElement, onSelectViewElement, graphics } = useVisuAgg({ data: visuElements });
useEffect(() => {
setSelectedElement(undefined);
setSelectedView(undefined);
}, [visuElements])
const headers = ['Symbol', "PosX", "PosY", "Höhe", "Breite", "Ebene", "Auswahl"]
const handlerSaveXaml = () => {
if (!selectedView) return;
api_ProconViews_UpdateXAML(selectedView.id, selectedView.xaml)
}
return (
<div>
<div className="flex gap-8">
<section>
<div>
XAML-Views
</div>
<div className="flex flex-col gap-1 w-50 ">
{viewNames.map((v) => (
<div
className="border-1 rounded p-1 bg-gray-100"
key={v.index}
onClick={() => onSelectViewElement(v.index)}
>
{v.name}
</div>
))}
</div>
</section>
<section>
<div>
{selectedViewElement?.viewType}
<div className="flex gap-4">
<div>X</div>
<div>{selectedViewElement?.posX}</div>
</div>
<div>
{graphics.map((g) => (<div>{g.viewType}-{g.viewIndex}-{g.state}</div>))}
</div>
<div>
{graphics.map((g) => <SvgView svg={g.svg} />)
}
</div>
<div>
{ graphics[0] &&
<textarea className="w-300 h-100" value={(graphics[0].xaml)} />
}
</div>
</div>
</section>
</div>
<div className="grid grid-cols-[200px_50px_50px_50px_50px_50px_400px] gap-4">
{headers.map((name) => <div key={name}>{name}</div>)}
</div>
{elements.map((element) => (
<div className="grid grid-cols-[200px_50px_50px_50px_50px_50px_400px] gap-4" key={element.id}>
<div>{element.viewType}{element.viewType == 'pfeil' && element.viewIndex}</div>
<VisualisierungPositionen id={element.id} koordinaten={{ ...element }}></VisualisierungPositionen>
<div>{element.views.length > 0 && <button onClick={() => setSelectedElement(() => element)}>x</button>}</div>
</div>
))
}
<div className="mt-8">
{selectedElement && <div>
<div className="font-bold p-4">View {selectedElement.viewType}{selectedElement.viewType == "pfeil" && selectedElement.viewIndex}</div>
<div className="flex gap-4 p-4">
{selectedElement.views.map((view) => <div onClick={() => setSelectedView(view)} key={view.id}>{view.state}</div>)}
</div>
</div>}
</div>
{selectedViewElement?.viewType}
{selectedViewElement && selectedViewElement.views &&
<div>
<div>
<textarea className="w-300 h-100" value={atob(selectedViewElement.views[0].xaml)} onChange={(e) => setSelectedView((old) => ({ ...old!, xaml: btoa(e.target.value) }))} />
</div>
<div>
<button onClick={() => handlerSaveXaml()}>Save</button>
<button onClick={() => setSelectedView((old) => undefined)}>cancel</button>
</div>
</div>
}
</div>
);
}
@@ -0,0 +1,35 @@
"use client";
import { useEffect, useState } from "react";
import Modal from "../ui/Modal";
interface Props {
xamlBase64Data: string
}
export default function AggregateXamlData({ xamlBase64Data }: Props) {
const [showDetails, setShowDetails] = useState(false);
const [text, setText] = useState('');
useEffect(() => {
setText(atob(xamlBase64Data))
}, [xamlBase64Data])
return (
<section>
<button onClick={() => setShowDetails(!showDetails)}>
{showDetails ? "Weniger Details" : "Mehr Details"}
</button>
{showDetails && (
<Modal onClose={()=>setShowDetails(false)}>
<div className="w-400 h-200">
<textarea className="w-full h-full" name="xaml" id="xaml" value={text} onChange={(e) => setText(e.target.value)}></textarea>
</div>
</Modal>
)}
</section>
);
}
+50
View File
@@ -0,0 +1,50 @@
"use client";
import { useEffect, useState } from "react";
import Modal from "../ui/Modal";
import { InputNumber } from "../ui/InputNumber";
import { api_ProconVisualisierungen_Update } from "@/actions/procon/api-procon-visualisierungen";
import { ProConView } from "@/types/procon";
interface Positionen{
}
interface Props {
data:ProConView
}
export default function EditXaml({ data }: Props) {
const [showDetails, setShowDetails] = useState(false);
const [text, setText] = useState('');
const handleChange = (name:string,value:number)=>{
console.log('Submit','name',name,' value',value);
let data :Partial<ProConView> = {}
if(name==='posX') data['posX'] = value;
if(name==='posY') data['posY'] = value;
if(name==='height') data['height'] = value;
if(name==='width') data['width'] = value;
if(name==='zIndex') data['zIndex'] = value;
//api_ProconVisualisierungen_Update(id,data);
}
return (
<div>
{data.views.map((view)=><div>{view.state}</div>)}
</div>
);
}
/*
<div>{element.posX}</div>
<div>{element.posY}</div>
<div>{element.height}</div>
<div>{element.width}</div>
<div>{element.zIndex}</div>
*/
+15
View File
@@ -0,0 +1,15 @@
'use client';
interface Props {
svg: string;
}
export default function SvgView({ svg }: Props) {
return (
<div
dangerouslySetInnerHTML={{
__html: svg,
}}
/>
);
}
View File
Whitespace-only changes.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,59 @@
"use client";
import { useEffect, useState } from "react";
import Modal from "../ui/Modal";
import { InputNumber } from "../ui/InputNumber";
import { api_ProconVisualisierungen_Update } from "@/actions/procon/api-procon-visualisierungen";
import { ProConView } from "@/types/procon";
interface Positionen{
posX:number,
posY:number,
height:number,
width:number,
zIndex:number
}
interface Props {
id:number,
koordinaten: Positionen
}
export default function VisualisierungPositionen({ id,koordinaten }: Props) {
const [showDetails, setShowDetails] = useState(false);
const [text, setText] = useState('');
const handleChange = (name:string,value:number)=>{
console.log('Submit','name',name,' value',value);
let data :Partial<ProConView> = {}
if(name==='posX') data['posX'] = value;
if(name==='posY') data['posY'] = value;
if(name==='height') data['height'] = value;
if(name==='width') data['width'] = value;
if(name==='zIndex') data['zIndex'] = value;
api_ProconVisualisierungen_Update(id,data);
}
return (
<>
<InputNumber name="posX" inValue={koordinaten.posX} onChanged={handleChange} />
<InputNumber name="posY" inValue={koordinaten.posY} onChanged={handleChange} />
<InputNumber name="height" inValue={koordinaten.height} onChanged={handleChange} />
<InputNumber name="width" inValue={koordinaten.width} onChanged={handleChange} />
<InputNumber name="zIndex" inValue={koordinaten.zIndex} onChanged={handleChange} />
</>
);
}
/*
<div>{element.posX}</div>
<div>{element.posY}</div>
<div>{element.height}</div>
<div>{element.width}</div>
<div>{element.zIndex}</div>
*/
+32
View File
@@ -0,0 +1,32 @@
"use client"
import { api_anlage_setSelected } from "@/actions/rk/api-anlage";
import { Anlage } from "@/types/rk";
interface Props {
data: Anlage,
isSelected: boolean
}
export default function AnlageColumn({ data, isSelected }: Props) {
return <div style={{ backgroundColor: isSelected ? "#00ff00" : "#ffffff" }}
onClick={() => api_anlage_setSelected(data.id)}>
<div>
{data.id}
</div>
<div>
{data.anlageNr}
</div>
<div>
{data.name}
</div>
</div>
}
@@ -0,0 +1,53 @@
"use client"
import { Anlage, Element, ExcelProCon, Group } from "@/types/rk";
import { useEffect, useState } from "react";
import { ElementEdit } from "./elementEdit";
import ElementViewItem from "./elementsViewItem";
import ElementViewListHeader from "./elementsViewListHeader";
import ElementSelectExcelList from "./elementsSelectExcelList";
import { ElementEditRow } from "./elementEditRow";
import { useElements } from "@/hooks/useElements";
interface Props {
analge:Anlage,
elements: Element[],
groups: Group[]
}
export default function ElementCreateView({ analge,elements:initElements, groups }: Props) {
const { elements, selectedElement, filterExcelListe, setFilterExcelListe, setSelectedItem } = useElements({ elements: initElements });
return (
<div className="grid grid-cols-[1200_300]">
<div>
<ElementSelectExcelList onSelect={(liste) => setFilterExcelListe(liste)}></ElementSelectExcelList>
<ElementEditRow anlage={analge} groups={groups}></ElementEditRow>
{filterExcelListe}
<div className="m-4 p-4">
<ElementViewListHeader></ElementViewListHeader>
<div className="h-180 overflow-y-scroll border">
{elements.map((item) =>
<ElementViewItem key={item.id} element={item} onSelect={setSelectedItem}></ElementViewItem>
)}
</div>
</div>
</div>
</div>
)
}
+141
View File
@@ -0,0 +1,141 @@
"use client";
import { useEffect, useState } from "react"
import { useForm } from "react-hook-form"
import { InputTextR } from "@/components/ui/inputTextR";
import { Element, ElementCreate, emptyElement, Group } from "@/types/rk";
import { api_elementDelete, elementCreate, elementUpdate } from "@/actions/rk/api-element";
import { SelectR } from "@/components/ui/selectR";
interface Params {
groups: Group[]
initData?: Element
debug?: boolean
}
export const ElementEdit = ({ initData, groups, debug }: Params) => {
const { control, register, handleSubmit, formState: { errors }, watch, getFieldState, reset } = useForm<ElementCreate>({ defaultValues: emptyElement, })
const [modeUpdate, setModeUpdate] = useState(false);
const [selGroup, setSelGroup] = useState<string | undefined>();
const [id, setId] = useState<string | undefined>();
const [error, setError] = useState<string | undefined>();
useEffect(() => {
if (initData) {
const { id, group, ...rest } = initData;
setId(id);
setModeUpdate(true);
if (initData.group) setSelGroup(() => initData.group.id);
reset({ ...rest, groupId: group ? group.id : undefined })
}
}, [initData]);
const onSubmit = async (data: ElementCreate) => {
setError(undefined);
//const { name, isConsumable, description } = data;
if (modeUpdate) {
if (id) {
elementUpdate(id, data);
} else {
setError('Etwas ist falsch, keine ID');
}
} else {
elementCreate(data);
}
}
const handlerDelete = async () => {
if (id) api_elementDelete(id);
}
const excelListe = [{ id: "keine", value: "" },
{ id: "Aggregate", value: "Aggregate" },
{ id: "Sensoren", value: "Sensoren" },
{ id: "Analogwerte", value: "Analogwerte" },
{ id: "AnlagenVisu", value: "AnlagenVisualisierung" },
]
const options = watch("options")
return (
<div className="w-120 flex flex-col p-4 pt-10 ps-10" >
<div className="border-1 p-4">
<form onSubmit={handleSubmit(onSubmit)} className="max-w-md mx-auto">
<div>
{initData && initData.group &&
<div>
{initData?.group.nr}_{initData?.group.name}
</div>}
<div>
<SelectR label="Gruppe" options={groups.map((g) => ({ id: g.id, value: g.nr + '_' + g.name }))} {...register("groupId")} ></SelectR>
</div>
</div>
<div className="grid grid-cols-[60px_1fr] gap-2">
<InputTextR label="Nr" {...register("excelNr", { required: "Pflichtfeld" })} error={errors.excelNr?.message} />
<InputTextR label="Name" {...register("name", { required: "Pflichtfeld" })} error={errors.name?.message} />
</div>
<InputTextR label="Prefix" {...register("prefix", { required: false })} error={errors.prefix?.message} />
<InputTextR label="funcTxt" {...register("funcTxt", { required: false })} error={errors.funcTxt?.message} />
<InputTextR label="Type" {...register("type", { required: "Pflichtfeld" })} error={errors.type?.message} />
<SelectR label="Excel Liste" options={excelListe.map((e) => ({ id: e.id, value: e.value }))} {...register("excelListe")} ></SelectR>
<div>
{error && <div>{error}</div>}
{errors.name && <div>{errors.name.message}</div>}
</div>
<div className="p-2 mt-4 ">
<div className="font-semibold">Optionen</div>
<div className="border-1 rounded border-gray-200 p-4 ">
{options &&
Object.entries(options).map(([key, value]) => (
<div key={key} className="grid grid-cols-[100px_200px] pt-1">
<label className="font-semibold">{key}:</label>
{typeof value === "boolean" ? (
<div className="flex flex-start ">
<input
className=""
type="checkbox"
{...register(`options.${key}` as const)}
/>
</div>
) : (
<div className="">
<InputTextR {...register(`options.${key}` as const)} />
</div>
)}
</div>))
}
</div>
</div>
<div className="flex gap-4">
<button type="submit" className="bg-blue-500 text-white p-2 rounded mt-2 hover:bg-blue-600">Speichern</button>
{id && <button className="bg-red-500 text-white p-2 rounded mt-2 hover:bg-blue-600" onClick={() => handlerDelete()}>Löschen</button>}
</div>
</form>
</div >
{debug &&
<div>
<div>ID: {id}</div>
<pre>{JSON.stringify(watch(), null, 2)}</pre>
</div>
}
</div >
)
}
@@ -0,0 +1,102 @@
"use client";
import { useEffect, useState } from "react"
import { useForm } from "react-hook-form"
import { InputTextR } from "@/components/ui/inputTextR";
import { Anlage, Element, ElementCreate, emptyElement, Group } from "@/types/rk";
import { api_elementDelete, elementCreate, elementUpdate } from "@/actions/rk/api-element";
import { SelectR } from "@/components/ui/selectR";
import { useElement } from "@/hooks/useElement";
import { Sites } from "@/_config/sites";
import Toast from "@/components/ui/Toast";
interface Params {
anlage: Anlage,
groups: Group[]
elementIntial?: Element
debug?: boolean
}
export const ElementEditRow = ({ anlage, elementIntial, groups, debug }: Params) => {
const { control, register, handleSubmit, formState: { errors }, watch, setFocus, reset } = useForm<ElementCreate>({ defaultValues: emptyElement, })
const { proConExcelLists } = useElement({ element: elementIntial })
const [selGroup, setSelGroup] = useState<string | undefined>();
const [toast, setToast] = useState<string | null>(null);
const [error, setError] = useState<string | undefined>();
useEffect(() => {
if (elementIntial) {
const { id, group, ...rest } = elementIntial;
if (elementIntial.group) setSelGroup(() => elementIntial.group.id);
reset({ ...rest, groupId: group ? group.id : undefined })
} else {
reset({ ...emptyElement, anlageId: anlage.id })
}
}, [elementIntial]);
const onSubmit = async (data: ElementCreate) => {
setError(undefined);
//await elementCreate(data);
setToast("Erfolgreich gespeichert!");
setFocus('excelNr')
}
const options = watch("options")
return (
<div className="flex flex-col p-4 pt-10 ps-10" >
<div className="relative inline-block">
{toast && (
<Toast
message={toast}
onClose={() => setToast(null)}
/>
)}
</div>
<form onSubmit={handleSubmit(onSubmit)} className="">
<div className="flex gap-4 border-1 border-gray-200 rounded-sm p-4 shadow-md">
<div className="grid grid-cols-[60px_1fr] gap-2">
<InputTextR label="Nr" {...register("excelNr", { required: "Pflichtfeld" })} error={errors.excelNr?.message} />
<InputTextR label="Name" {...register("name", { required: "Pflichtfeld" })} error={errors.name?.message} />
</div>
<InputTextR label="Prefix" {...register("prefix", { required: false })} error={errors.prefix?.message} />
<InputTextR label="funcTxt" {...register("funcTxt", { required: false })} error={errors.funcTxt?.message} />
<InputTextR label="Type" {...register("type", { required: "Pflichtfeld" })} error={errors.type?.message} />
<SelectR label="Excel Liste" options={proConExcelLists} {...register("excelListe")} ></SelectR>
<div>
{error && <div>{error}</div>}
{errors.name && <div>{errors.name.message}</div>}
</div>
<div className="flex gap-4">
<button type="submit" className="bg-blue-500 text-white p-2 rounded mt-2 hover:bg-blue-600">Speichern</button>
</div>
</div >
</form>
{true &&
<div>
<pre>{JSON.stringify(watch(), null, 2)}</pre>
</div>
}
</div >
)
}
@@ -0,0 +1,28 @@
import { Element, Group } from "@/types/rk";
interface Props {
elements: Element[],
onSelect?: (element:Element)=>void
}
export default function ElementSelect({ elements,onSelect }: Props) {
const handleSelect = (element:Element)=>{
if(onSelect) onSelect(element);
}
return (
<div className="">
<div className="flex flex-col m-4 pt-4 overflow-y-scroll border h-150">
{elements.map((el) => (
<div className="flex hover:bg-sky-100 h-8 items-center" onClick={()=>handleSelect(el)} key={el.id}>
<div className="ps-4 ">{`${el.group ? el.group.nr:'noGroup'}${el.prefix ? el.prefix:""} ${el.name}`}</div>
</div>))}
</div>
</div>
)
}
@@ -0,0 +1,27 @@
"use client"
import { ExcelProCon } from "@/types/rk";
import MonButton from "@/components/ui/MonBotton";
interface Props {
onSelect:(excelListitem:ExcelProCon)=> void
}
export default function ElementSelectExcelList({ onSelect }: Props) {
return (
<div className="flex gap-4 p-4">
{Object.values(ExcelProCon).map((item) => (
<MonButton key={item} onClick={()=>onSelect(item)}>
{item}
</MonButton>
))}
</div>
)
}
@@ -0,0 +1,50 @@
"use client"
import { Element, ExcelProCon, Group } from "@/types/rk";
import { useEffect, useState } from "react";
import { ElementEdit } from "./elementEdit";
import ElementViewItem from "./elementsViewItem";
import ElementViewListHeader from "./elementsViewListHeader";
import ElementSelectExcelList from "./elementsSelectExcelList";
import { useElements } from "@/hooks/useElements";
interface Props {
elements: Element[],
groups: Group[]
}
export default function ElementView({ elements: initElements, groups }: Props) {
const { elements, selectedElement, filterExcelListe, setFilterExcelListe, setSelectedItem } = useElements({ elements: initElements });
return (
<div className="grid grid-cols-[1200_300]">
<div>
<ElementSelectExcelList onSelect={(liste) => setFilterExcelListe(liste)}></ElementSelectExcelList>
{filterExcelListe}
<div className="m-4 p-4">
<ElementViewListHeader></ElementViewListHeader>
<div className="h-180 overflow-y-scroll border">
{elements.map((item) =>
<ElementViewItem key={item.id} element={item} onSelect={setSelectedItem}></ElementViewItem>
)}
</div>
</div>
</div>
{selectedElement &&
<div>
<ElementEdit initData={selectedElement} debug={true} groups={groups}></ElementEdit>
</div>
}
</div>
)
}
@@ -0,0 +1,48 @@
"use client"
import { Element, ExcelProCon, Group } from "@/types/rk";
import { useEffect, useState } from "react";
import { ElementEdit } from "./elementEdit";
interface Props {
element: Element,
onSelect: (element: Element) => void
}
export default function ElementViewItem({ element, onSelect }: Props) {
const cols = [180, 80, 200, 300, 300, 150];
const style = { gridTemplateColumns: cols.map(c => `${c}px`).join(" ") }
return (
<div className="grid grid-cols-[1200_300]" style={style} onClick={() => onSelect(element)}>
<div>{element.excelListe}</div>
<div>{element.excelNr}</div>
{element.group ? <div>{element.group.nr} {element.group.name}</div> : <div>-</div>}
<div>{element.name}</div>
{/* Type */}
{element.group ? <div>
G_{element.group.nr}
{element.prefix != undefined && element.prefix !== "" && "_" + element.prefix}
_{element.group.name}
{element.funcTxt != undefined && element.funcTxt !== "" && "_" + element.funcTxt}
</div> : <div>n.v.</div>}
{<div>{element.type}</div>}
</div>
)
}
@@ -0,0 +1,40 @@
"use client"
import { Element, ExcelProCon, Group } from "@/types/rk";
import { useEffect, useState } from "react";
import { ElementEdit } from "./elementEdit";
import ElementViewItem from "./elementsViewItem";
interface Props {
}
export default function ElementViewListHeader({ }: Props) {
const cols = [180, 80, 200, 300, 300, 150];
const style = { gridTemplateColumns: cols.map(c => `${c}px`).join(" ") };
const headers = [
{ id: 0, name: "Excel Liste" },
{ id: 1, name: "Excel Nr" },
{ id: 2, name: "Gruppe" },
{ id: 3, name: "Element" },
{ id: 4, name: "ProConName" },
{ id: 5, name: "Typ" },
]
return (
<div className={`grid font-bold `} style={style}>
{headers.map((col) => <div key={col.id}>{col.name}</div>)}
</div>
)
}
+28
View File
@@ -0,0 +1,28 @@
import { Group } from "@/types/rk";
interface Props {
groups: Group[],
onSelect?: (id:Group)=>void
}
export default function GroupSelect({ groups,onSelect }: Props) {
const handleSelect = (id:Group)=>{
if(onSelect) onSelect(id);
}
return (
<div className="">
<div className="flex flex-col m-4 pt-4 overflow-y-scroll border h-150">
{groups.map((group) => (
<div className="flex hover:bg-sky-100 h-8 items-center" onClick={()=>handleSelect(group)} key={group.id}>
<div className="ps-4 ">{`${group.nr} ${group.name}`}</div>
</div>))}
</div>
</div>
)
}
+65
View File
@@ -0,0 +1,65 @@
"use client"
import { Group } from "@/types/rk";
import GroupSelect from "./groupSelect";
import { useEffect, useState } from "react";
import GroupViewItem from "./group_ViewItem";
import GroupFilter from "./group_Filter";
import { GroupEdit } from "./group_Edit";
interface Props {
groups: Group[],
}
export default function GroupView({ groups }: Props) {
const [selectedItem, setSelectedItem] = useState<Group | undefined>();
const [groupFilterd, setGroupFilterd] = useState<Group[]>([]);
useEffect(() => {
setGroupFilterd([...groups]);
if(selectedItem){
const updatedGroup = groups.find((g)=>g.id==selectedItem.id);
if(updatedGroup) setSelectedItem(()=>updatedGroup);
}
}, [groups])
const handlerOnChangeFilter = (groups: Group[]) => {
setGroupFilterd([...groups]);
}
const handleSelect = (item: Group) => {
console.log(item);
setSelectedItem(item);
}
return (
<div>
<div className="m-4 p-4 border-1 border-gray-100 w-256">
<GroupFilter groups={groups} onChange={handlerOnChangeFilter}></GroupFilter>
</div>
<div className="m-4 grid grid-cols-[300_2fr]">
{/** Spalte 1 Auswahl Gruppe */}
<div>
<GroupSelect onSelect={handleSelect} groups={groupFilterd}></GroupSelect>
</div>
{/** Spalte 2 Anzeige Gruppe details */}
<div className="flex flex-col w-256 h-10">
{selectedItem && <div >
<div>edit</div>
<GroupViewItem group={selectedItem} ></GroupViewItem>
{<GroupEdit debug={true} initData={selectedItem} ></GroupEdit>}
</div>}
</div>
</div>
</div>
)
}
+124
View File
@@ -0,0 +1,124 @@
"use client";
import { useEffect, useState } from "react"
import { useForm } from "react-hook-form"
import { InputTextR } from "@/components/ui/inputTextR";
import { Anlage, emptyGroup, Group, GroupCreate } from "@/types/rk";
import { groupCreate, groupUpdate } from "@/actions/rk/api-groups";
interface Params {
anlage:Anlage
initData?: Group
debug?: boolean
}
export const GroupEdit = ({anlage, initData, debug }: Params) => {
const { control, register, handleSubmit, formState: { errors }, watch, getFieldState, reset } = useForm<GroupCreate>({ defaultValues: emptyGroup, })
const [modeUpdate, setModeUpdate] = useState(false);
const [id, setId] = useState<string | undefined>();
const [error, setError] = useState<string | undefined>();
useEffect(() => {
if (initData) {
const { id, ...rest } = initData;
setId(id);
setModeUpdate(true);
reset({ ...rest })
}else{
reset({ ...emptyGroup,anlageId:anlage.id })
}
}, [initData]);
const onSubmit = async (data: GroupCreate) => {
setError(undefined);
//const { name, isConsumable, description } = data;
if (modeUpdate) {
if (id) {
groupUpdate(id, data);
} else {
setError('Etwas ist falsch, keine ID');
}
} else {
console.log('create',data)
groupCreate(data);
}
}
const options = watch("options")
return (
<div className="w-120 flex flex-col p-4 pt-10 ps-10" >
<div className="border-1 p-4">
<form onSubmit={handleSubmit(onSubmit)} className="max-w-md mx-auto">
<div className="grid grid-cols-[60px_1fr] gap-2">
<InputTextR label="Nr" {...register("nr", { required: "Pflichtfeld" })} error={errors.nr?.message} />
<InputTextR label="Name" {...register("name", { required: "Pflichtfeld" })} error={errors.name?.message} />
</div>
<InputTextR label="Anzeige Name" {...register("displayName", {})} error={errors.displayName?.message} />
<InputTextR label="Info" {...register("info", { required: "Pflichtfeld" })} error={errors.info?.message} />
<InputTextR label="Ort" {...register("ort", { required: "Pflichtfeld" })} error={errors.ort?.message} />
<InputTextR label="Type" {...register("type", { required: "Pflichtfeld" })} error={errors.type?.message} />
<div>
{error && <div>{error}</div>}
{errors.name && <div>{errors.name.message}</div>}
</div>
<div className="p-2 mt-4 ">
<div className="font-semibold">Optionen</div>
<div className="border-1 rounded border-gray-200 p-4 ">
{options &&
Object.entries(options).map(([key, value]) => (
<div key={key} className="grid grid-cols-[100px_200px] pt-1">
<label className="font-semibold">{key}:</label>
{typeof value === "boolean" ? (
<div className="flex flex-start ">
<input
className=""
type="checkbox"
{...register(`options.${key}` as const)}
/>
</div>
) : (
<div className="">
<InputTextR {...register(`options.${key}` as const)} />
</div>
)}
</div>))
}
</div>
</div>
<button type="submit" className="bg-blue-500 text-white p-2 rounded mt-2 hover:bg-blue-600">Speichern</button>
</form>
</div >
{debug &&
<div>
<div>{watch("name")}</div>
<pre>touched:{getFieldState("displayName").isTouched ? 'true' : 'false'}</pre>
<pre>{JSON.stringify(watch(), null, 2)}</pre>
</div>
}
</div >
)
}
/*
<div className="mt-4 mb-4 p-2 border-1 border-gray-200 relative ">
<div className="absolute -top-2 left-2 bg-white ps-1 pe-1 text-xs">Optionen</div>
<InputCheckboxR label="Konsumierbar" {...register("isConsumable")} error={errors.isConsumable?.message} />
</div>
*/
+36
View File
@@ -0,0 +1,36 @@
"use client"
import { Group } from "@/types/rk";
import { useEffect, useState } from "react";
interface Props {
groups: Group[],
onChange: (filterdGroups:Group[])=>void
}
export default function GroupFilter({ groups,onChange }: Props) {
const [txt,setText] = useState('');
useEffect(()=>{
const filterd = groups.filter((group)=>{
let found = false;
if(group.name.includes(txt)) found = true;
if(group.nr.includes(txt)) found = true;
return found
})
onChange(filterd);
},[txt,groups])
return (
<div className="">
<div className="text-sm font-semibold">Suche</div>
<input onChange={(e)=>setText(e.target.value)} value={txt} className="w-96 border rounded p-2 border-gray-300" />
</div>
)
}
+37
View File
@@ -0,0 +1,37 @@
"use client"
import { Group } from "@/types/rk";
import GroupListItem from "./group_List_Item";
interface Props {
groups: Group[]
}
export default function GroupList({ groups }: Props) {
return (
<div className="m-16 ms-32 w-256 bg-gray-0 border-t-1 border-gray-100 shadow-xl">
<div className="flex flex-col m-4 gap-2">
<div className="grid grid-cols-[200px_200px_250px_1fr] border-b-1 border-gray-800 ps-4 pb-1 pt-1 text-lg font-bold" >
<div>ID</div>
<div>Name</div>
<div>Optionen</div>
<div>Aggregate</div>
</div>
{groups.map((group) => (
<div className="grid grid-cols-[200px_200px_250px_1fr] border-b-1 min-h-16 border-gray-200 ps-4 pb-4 pt-2" key={group.id}>
<GroupListItem item={group}></GroupListItem>
</div>))}
</div>
</div>
)
}
@@ -0,0 +1,57 @@
"use client"
import { Group } from "@/types/rk";
interface Props {
item: Group
}
export default function GroupListItem({ item }: Props) {
return (
<>
<div>
{item.nr}
</div>
<div>
{item.name}
</div>
<div>
{item.options &&
Object.entries(item.options).map(([key, value]) => (
<div key={key} className="grid grid-cols-2 gap-8">
<div >{key}</div>
<div>
{typeof value === "boolean" ? (value ? "Ja" : "Nein") : value}
</div>
</div>
))}
</div>
<div className="">
{item.elements && <div>
{item.elements.map((element) => (
<div key={element.id} className="grid grid-cols-2 gap-8">
{false && element.prefix && <div>
{element.prefix}
</div>}
{element.name}
<div>
{element.excelNr}
</div>
</div>))}
</div>}
</div>
</>
)
}
+131
View File
@@ -0,0 +1,131 @@
"use client"
import { Group, Peripherie, PeripherieFilterOptionen } from "@/types/rk";
import { useEffect, useState } from "react";
import PeripherieListView from "../peripherie/peripherieListView";
import { PeripherieFindApi } from "@/actions/rk/api-peripherie";
import { api_GroupAll, api_groupDelete } from "@/actions/rk/api-groups";
interface Props {
group: Group
}
export default function GroupViewItem({ group }: Props) {
const [showEA_Group, setShowEA_Group] = useState(false);
const [showEA_Element, setShowEA_Element] = useState(false);
const [showEA_Diff, setShowEA_Diff] = useState(false);
const [peripheries, setPeripheries] = useState<Peripherie[]>([]);
useEffect(() => {
setPeripheries([]);
setShowEA_Group(false);
setShowEA_Element(false);
setShowEA_Diff(false);
}, [group])
const handlerShow_Group = () => {
const newMode = !showEA_Group;
setShowEA_Group(newMode)
if (newMode) {
let filter: PeripherieFilterOptionen = {
groupId: group.id
}
PeripherieFindApi(filter).then(setPeripheries);
} else {
setPeripheries([])
};
};
const handlerShow_Element = () => {
setShowEA_Element((pre) => !pre)
//if (filterElementId) filter['elementId'] = filterElementId;
if (showEA_Group) {
let filter: PeripherieFilterOptionen = {
// elementId: group..id
};
// PeripherieFindApi(filter).then(setPeripheries);
}
};
const handlerShow_Diff = () => {
setShowEA_Diff((pre) => !pre)
};
const handlerDelete = () => {
if(group.id) api_groupDelete(group.id);
};
return (
<div className="ms-2">
<div className="flex gap-2 text-lg font-semibold bg-gray-200 ps-2">
<div>Gruppe :</div>
<div>{group.nr}</div>
<div>{group.name}</div>
</div>
<div className="flex flex-col gap-2">
<div>Anzeige Name :{group.displayName}</div>
<div>Info: {group.info}</div>
<div>Ort: {group.ort}</div>
<div>Type {group.type}</div>
</div>
<div className="mt-2 ps-2">
<div className="text-lg font-semibold">Eigenschaften</div>
{group.options &&
Object.entries(group.options).map(([key, value]) => (
<div key={key} className="grid grid-cols-2 gap-8">
<div >{key}</div>
<div>
{typeof value === "boolean" ? (value ? "Ja" : "Nein") : value}
</div>
</div>
))}
</div>
<div className="mt-2 ps-2">
<div className="text-lg font-semibold">Elemente</div>
{group.elements && <div>
{group.elements.map((element) => (
<div key={element.id} className="grid grid-cols-2 gap-8">
{false && element.prefix &&
<div> {element.prefix}</div>
}
<div>{element.name}</div>
<div>{element.excelNr}</div>
</div>))}
</div>}
</div>
<div className="mt-8">
<button className="bg-gray-300 rounded-xl p-2 hover:bg-gray-500 me-2" onClick={() => handlerShow_Group()}>Show EA Group</button>
<button className="bg-gray-300 rounded-xl p-2 hover:bg-gray-500 me-2" onClick={() => handlerShow_Element()}>Show EA Elements</button>
<button className="bg-red-300 rounded-xl p-2 hover:bg-gray-500 me-2" onClick={() => handlerDelete()}>Löschen</button>
<button className="bg-gray-300 rounded-xl p-2 hover:bg-gray-500 me-2">Show EA Diffrences to Excel</button>
</div>
{showEA_Group && <div>
Ein-Ausgänge
</div>}
{showEA_Group && <div className="border-1">
</div>}
</div>
)
}
+117
View File
@@ -0,0 +1,117 @@
"use client";
import { useEffect, useState } from "react"
import { useFieldArray, useForm } from "react-hook-form"
import { InputTextR } from "@/components/ui/inputTextR";
import { InputCheckboxR } from "@/components/ui/inputCheckboxR";
import { Anlage, emptyGroup, Group, GroupCreate } from "@/types/rk";
import { groupCreate, groupUpdate } from "@/actions/rk/api-groups";
import { api_anlage_getSelected } from "@/actions/rk/api-anlage";
import { useFocusManager } from "@/components/ui/editable/useFocusManager";
interface Params {
anlage:Anlage
defaultGroup?: Group
debug?: boolean
}
export const GroupBulkEdit = ({anlage, defaultGroup, debug }: Params) => {
const { control, register, handleSubmit, formState: { errors }, watch, getFieldState, reset } = useForm<GroupCreate>({ defaultValues: emptyGroup, })
const [error, setError] = useState<string | undefined>();
useEffect(() => {
if(defaultGroup){
const {id,elements,options,...defaults} = defaultGroup;
reset({ ...emptyGroup,...defaults })
}
}, []);
const onSubmit = async (data: GroupCreate) => {
setError(undefined);
groupCreate(data);
}
const options = watch("options")
const focus = useFocusManager();
return (
<div className="w-120 flex flex-col p-4 pt-10 ps-10" >
<div className="border-1 p-4">
<form onSubmit={handleSubmit(onSubmit)} className="max-w-md mx-auto">
<div className="grid grid-cols-[60px_1fr] gap-2">
<InputTextR label="Nr" {...register("nr", { required: "Pflichtfeld" })} error={errors.nr?.message} />
<InputTextR label="Name" {...register("name", { required: "Pflichtfeld" })} error={errors.name?.message} />
</div>
<InputTextR label="Anzeige Name" {...register("displayName", {})} error={errors.displayName?.message} />
<InputTextR label="Info" {...register("info", { required: "Pflichtfeld" })} error={errors.info?.message} />
<InputTextR label="Ort" {...register("ort", { required: "Pflichtfeld" })} error={errors.ort?.message} />
<InputTextR label="Type" {...register("type", { required: "Pflichtfeld" })} error={errors.type?.message} />
<div>
{error && <div>{error}</div>}
{errors.name && <div>{errors.name.message}</div>}
</div>
<div className="p-2 mt-4 ">
<div className="font-semibold">Optionen</div>
<div className="border-1 rounded border-gray-200 p-4 ">
{options &&
Object.entries(options).map(([key, value]) => (
<div key={key} className="grid grid-cols-[100px_200px] pt-1">
<label className="font-semibold">{key}:</label>
{typeof value === "boolean" ? (
<div className="flex flex-start ">
<input
className=""
type="checkbox"
{...register(`options.${key}` as const)}
/>
</div>
) : (
<div className="">
<InputTextR {...register(`options.${key}` as const)} />
</div>
)}
</div>))
}
</div>
</div>
<button type="submit" className="bg-blue-500 text-white p-2 rounded mt-2 hover:bg-blue-600">Speichern</button>
</form>
</div >
{debug &&
<div>
<div>{watch("name")}</div>
<pre>touched:{getFieldState("displayName").isTouched ? 'true' : 'false'}</pre>
<pre>{JSON.stringify(watch(), null, 2)}</pre>
</div>
}
</div >
)
}
/*
<div className="mt-4 mb-4 p-2 border-1 border-gray-200 relative ">
<div className="absolute -top-2 left-2 bg-white ps-1 pe-1 text-xs">Optionen</div>
<InputCheckboxR label="Konsumierbar" {...register("isConsumable")} error={errors.isConsumable?.message} />
</div>
*/
@@ -0,0 +1,50 @@
"use client"
import { useEditableList } from "@/components/ui/editable-list/useEditableList";
import { Group } from "@/types/rk";
import { useRef } from "react";
import { GroupEditRow } from "./group_edit_row";
interface Props {
data: Group[],
}
export const GroupEditList = ({ data }: Props) => {
const listRef = useRef<HTMLDivElement>(null);
const list = useEditableList({
ref: listRef, data: data, onSave: async (group) => {
//await anlageApiUpdate(anlage.id, { anlageNr: anlage.anlageNr, name: anlage.name });
console.log(group)
}
});
return (
<div className="flex flex-col w-400 p-4 border-1 border-gray-200 focus:outline-none focus:ring-1 focus:ring-sky-100 rounded focus:border-1 "
ref={listRef}
tabIndex={0}
onKeyDown={list.onKeyDown}
>
{data.map((a, index) => (
<GroupEditRow
key={a.id}
data={a}
options={{ col1_w: 250, col2_w: 350,col3_w: 350, col: true }}
selected={list.isItemSelected(index)}
editing={list.isItemEditing(index)}
onClick={() => list.select(index)}
onSave={list.save}
onCancel={list.cancelEdit}
/>
))}
</div>)
}
+121
View File
@@ -0,0 +1,121 @@
import { EditableRow } from "@/components/ui/editable/EditableRow";
import { NumberCell } from "@/components/ui/editable/NumberCell";
import { TextCell } from "@/components/ui/editable/TextCell";
import { useFocusManager } from "@/components/ui/editable/useFocusManager";
import { emptyGroup, Group } from "@/types/rk";
import { useEffect, useState } from "react";
interface Props {
data: Group,
editing: boolean,
selected: boolean,
onSave: (value: Group) => void;
onCancel: () => void;
onClick: () => void;
options?: {
col1_w?: number
col2_w?: number,
col3_w?: number,
col4_w?: number,
col5_w?: number,
col6_w?: number,
col7_w?: number,
full?: boolean,
col?: boolean,
}
}
export const GroupEditRow = ({ data, selected, editing, options, onSave, onCancel }: Props) => {
const [id, setId] = useState('');
const [editData, setEditData] = useState(emptyGroup);
const focus = useFocusManager();
const col1w = options?.col1_w ? options?.col1_w : 100
const col2w = options?.col2_w ? options?.col2_w : 100
const col3w = options?.col3_w ? options?.col3_w : 100
const col4w = options?.col4_w ? options?.col4_w : 100
useEffect(() => {
const { id, ...anlageData } = data;
setEditData(() => ({ ...anlageData }));
setId(() => id);
}, [data]);
useEffect(() => {
if (editing) {
focus.focusFirst();
}
}, [editing]);
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
switch (e.key) {
case "Enter":
e.preventDefault();
onSave({ ...data, ...editData })
break;
case "Escape":
e.preventDefault();
onCancel();
break;
case "Tab":
e.preventDefault();
if (e.shiftKey) focus.focusPrevious(e.currentTarget);
else focus.focusNext(e.currentTarget);
break;
}
}
return (
<EditableRow selected={selected} columns={`${col1w}px ${col2w}px ${col3w}px ${col4w}px`}>
<NumberCell
ref={focus.register(0)}
editing={editing}
value={editData.nr.toString()}
onChange={(value) => setEditData((o) => ({ ...o, nr: value }))}
onKey={handleKeyDown}
/>
<TextCell
ref={focus.register(1)}
editing={editing}
value={editData.name}
onChange={(value) => setEditData((o) => ({ ...o, name: value }))}
onKey={handleKeyDown}
/>
<TextCell
ref={focus.register(2)}
editing={editing}
value={editData.funcTxt ? editData.funcTxt : '' }
onChange={(value) => setEditData((o) => ({ ...o, funcTxt: value }))}
onKey={handleKeyDown}
/>
<TextCell
ref={focus.register(3)}
editing={editing}
value={editData.info ? editData.info : '' }
onChange={(value) => setEditData((o) => ({ ...o, info: value }))}
onKey={handleKeyDown}
/>
</EditableRow>
)
}
+27
View File
@@ -0,0 +1,27 @@
"use client"
import { LogNote } from "@/types/rk";
import { useEffect, useState } from "react";
import LogNoteListItem from "./logNoteListItem";
interface Props {
notes: LogNote[],
}
export default function LogNoteList({ notes }: Props) {
return (
<div>
{notes.map((item)=><LogNoteListItem note={item} key={item.id}></LogNoteListItem>)}
</div>
)
}
@@ -0,0 +1,35 @@
"use client"
import { LogNote } from "@/types/rk";
import { useEffect, useState } from "react";
interface Props {
note: LogNote,
}
export default function LogNoteListItem({ note }: Props) {
return (
<div className="m-4 grid grid-cols-[300_2fr]">
{/** Spalte 1 Topic */}
<div>
{note.topic}
</div>
<div>
{note.status}
</div>
</div>
)
}
+29
View File
@@ -0,0 +1,29 @@
"use client"
import { LogNote } from "@/types/rk";
import { useEffect, useState } from "react";
import LogNoteList from "./logNoteList";
interface Props {
notes: LogNote[],
}
export default function LogNoteView({ notes }: Props) {
return (
<div className="m-4 p-4 border-1 border-gray-100 w-256">
<LogNoteList notes={notes}></LogNoteList>
</div>
)
}
@@ -0,0 +1,76 @@
"use client"
import { Group, GroupSubElement, Peripherie, PeripherieFilterOptionen, PeripherieUpdate } from "@/types/rk";
import { useEffect, useState } from "react";
import { api_peripherieUpdate, PeripherieFindApi } from "@/actions/rk/api-peripherie";
import { useStringInput } from "@/hooks/useStringInput";
interface Props {
peripheries: Peripherie
groups: Group[]
}
export default function PeripherieListInput({ peripheries, groups }: Props) {
const [filteredGroups, setFilteredGroups] = useState<Group[]>([]);
const nameInput = useStringInput({ value: peripheries.name, fallback: "", onCommit: (value) => api_peripherieUpdate(peripheries.id, { name: value }) });
const functionTxtInput = useStringInput({ value: peripheries.functionTxt, fallback: "", onCommit: (value) => api_peripherieUpdate(peripheries.id, { functionTxt: value }) });
const textInput = useStringInput({ value: peripheries.text, fallback: "", onCommit: (value) => api_peripherieUpdate(peripheries.id, { text: value }) });
useEffect(() => {
setFilteredGroups(groups);
}, [groups])
const handleChangeGroup = (id: string, value: string) => {
api_peripherieUpdate(id, { groupId: value })
console.log(id, { groupId: value });
}
//if(peripheries.excelData?.functionText === peripheries.text) return < ></>
return (
<div className="grid grid-cols-[300px_250px_250px_350px_200px_600px_10px_600px]">
<div>
<select onChange={(e) => handleChangeGroup(peripheries.id, e.target.value)} value={peripheries.group?.id}>
{
filteredGroups.map((group) => <option key={group.id} value={group.id} >{group.nr}_{group.name}</option>)
}
</select>
</div>
<div>
{peripheries.element?.name}
</div>
<div>
<input type="text" {...nameInput} name="name" />
</div>
<div className="border-1 flex w-full">
<input type="text" {...functionTxtInput} name="functionTxt" />
</div>
<div>
{peripheries.adrType}{peripheries.adrByte}.{peripheries.adrBit}
</div>
<div className="border-1 flex">
<input type="text" {...textInput} name="text" className="w-full" />
</div>
<div className="bg-gray-100"></div>
<div>
{peripheries.excelData?.functionText}
</div>
</div>
)
}
@@ -0,0 +1,42 @@
"use client"
import { Element, Group, GroupSubElement, Peripherie, PeripherieFilterOptionen } from "@/types/rk";
import { useEffect, useState } from "react";
import { api_peripherieUpdate, PeripherieFindApi } from "@/actions/rk/api-peripherie";
import PeripherieListInput from "./peripherieListInput";
interface Props {
peripheries:Peripherie[]
groups:Group[]
elemente:Element[]
}
export default function PeripherieListView({peripheries, groups }: Props) {
const [filteredGroups,setFilteredGroups] = useState<Group[]>([]);
useEffect(()=>{
setFilteredGroups(groups);
},[groups])
const handleChangeGroup = (id:string,value:string)=>{
api_peripherieUpdate(id,{groupId:value})
console.log(id,{groupId:value});
}
return (
<div>
{peripheries.map((p) => (<div key={p.id} className="grid grid-cols-[100px_150px_100px_150px_100px_200px_200px_250px_250px_200px_400px]">
<PeripherieListInput peripheries={p} groups={groups} key={p.id} />
</div>))}
</div>
)
}
@@ -0,0 +1,76 @@
"use client";
import { Anlage } from "@/types/rk";
import Modal from "@/components/ui/Modal";
import { GroupResponse } from "@/types/anlagen/groups";
import { useEffect, useState } from "react";
import { useGroups } from "@/hooks/rk2/useGroups";
import { UnitResponse } from "@/types/anlagen/units";
import { useUnit } from "@/hooks/rk2/useUnit";
import { ComponentResponse } from "@/types/anlagen/components";
interface Params {
anlage?: Anlage,
groups: GroupResponse[],
units: UnitResponse[]
}
export const AusgabeUnitInfo = ({ anlage, groups, units }: Params) => {
const {getPathToRoot} = useGroups({groups})
const getUnitInfo = (unit:UnitResponse) =>{
let groupId = unit.groupId;
let uGroupPath = getPathToRoot(groupId);
let unitName = [unit.prefix,unit.bmk,unit.name]
if(unit.postfix) unitName.push(unit.postfix)
let typ = unit.type;
//--- Components
let txt = "";
txt = uGroupPath.map((g)=>g.name).join('\t')+'\t';
txt += "\t" + unitName.join('_')+'\t';
//txt += "\tTyp:" + typ+'\n';
//txt += "\t\tKomponenten:"+'\n'
unit.components.forEach((comp)=>{
let cName:string[] = []
comp.prefix && cName.push(comp.prefix);
comp.name && cName.push(comp.name + (comp.index ? comp.index.toString():""));
//comp.index && cName.push();
comp.postfix && cName.push(comp.postfix);
//,comp.name,comp.index,comp.postfix
txt += '\t' + cName.join('_')+''
})
return txt;
}
const getComponentData = (comp:ComponentResponse) =>{
const name = [comp.prefix, comp.name,comp.index, comp.postfix]
}
const getText = ()=>{
let txt = ""
units.forEach((u)=> txt += getUnitInfo(u)+`\n`)
return txt;
}
return (
<div>
<textarea className="w-full h-100" value={getText()} />
</div >
)
}
Loaded 100 of 199 files, more files were not shown because too many files have changed in this diff. Show more