release v2.0.0 #4

Merged
crimsen merged 481 commits from develop into master 2024-01-18 15:15:08 +00:00
83 changed files with 1470 additions and 1267 deletions
Showing only changes of commit 0a2be0e5ff - Show all commits

3
.gitignore vendored
View File

@ -2,6 +2,9 @@
.thumbs.db
node_modules
# We use yarn, so ignore npm
package-lock.json
# Quasar core related directories
.quasar
/dist

View File

@ -13,6 +13,7 @@
"@quasar/extras": "^1.9.10",
"@vue/composition-api": "^0.6.4",
"axios": "^0.21.0",
"cordova": "^10.0.0",
"core-js": "^3.7.0",
"quasar": "^1.14.5",
"vue-router": "3.3.2"

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="flaschengeist-logo-dark.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="427.27412"
inkscape:cy="505.5186"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<g
id="g875"
transform="matrix(0.04643093,-0.35277275,0.35277275,0.04643093,7.9696963,229.75997)"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
<g
id="g819"
transform="translate(-233.70212,301.68819)"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
<path
id="path817"
d="m 206.836,94.524 c 0,-17.132 0,-94.524 0,-94.524 h -12.723 -7.534 -12.717 c 0,0 0,77.392 0,94.524 0,17.132 -32.207,37.929 -32.207,62.346 0,24.411 0,201.009 0,207.236 0,6.229 4.166,16.615 13.501,16.615 6.355,0 22.012,0 31.435,0 4.45,0 7.534,0 7.534,0 9.423,0 25.073,0 31.429,0 9.342,0 13.513,-10.387 13.513,-16.615 0,-6.228 0,-182.826 0,-207.236 -0.001,-24.417 -32.231,-45.203 -32.231,-62.346 z m -25.857,27.757 c -2.085,2.847 -4.415,6.437 -6.947,10.579 -2.562,4.153 -4.99,8.993 -7.459,14.244 -0.941,2.684 -1.906,5.496 -2.713,8.313 -0.029,2.986 -0.976,5.188 -0.563,8.935 0.25,6.948 0.523,14.256 0.801,21.82 0.5,15.133 0.593,31.284 0.721,47.422 -0.035,32.287 -1.011,64.588 -2.521,88.801 -0.662,12.107 -1.748,22.191 -2.26,29.256 -0.627,7.064 -1.15,11.107 -1.15,11.107 0,0 -0.528,-4.043 -1.156,-11.107 -0.511,-7.064 -1.598,-17.148 -2.26,-29.256 -1.51,-24.213 -2.487,-56.514 -2.521,-88.801 0.116,-16.139 0.209,-32.289 0.72,-47.422 0.279,-7.563 0.552,-14.872 0.802,-21.82 -0.122,-3.224 1.092,-7.604 1.784,-11.09 1.342,-3.282 2.829,-6.327 4.247,-9.196 1.726,-2.638 3.358,-5.165 4.926,-7.564 1.784,-2.19 3.474,-4.258 5.054,-6.222 3.288,-3.788 6.21,-6.989 8.761,-9.487 5.042,-5.042 8.278,-7.517 8.278,-7.517 0,0 -2.524,3.224 -6.544,9.005 z"
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
<g
id="g821"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g823"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g825"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g827"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g829"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g831"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g833"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g835"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g837"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g839"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g841"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g843"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g845"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g847"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g849"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
</g>
<path
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.44061947;stroke-opacity:1"
d="m 154.04963,46.75413 c -2.62014,0.516924 -5.22545,1.168424 -7.80617,1.95206 -42.75163,12.981828 -43.91253,68.68501 -54.129684,109.64785 -13.512037,49.65839 -58.120549,32.45922 -53.364321,57.25551 4.247764,22.14545 69.262455,44.34715 71.908285,44.72513 7.43909,1.06272 -52.780019,-26.79368 -40.437456,-42.16974 10.871821,-13.54384 54.907216,-1.28617 101.792266,-18.34148 41.23972,-15.24969 76.0405,-52.05406 67.35884,-95.66274 -8.0739,-40.556134 -44.95534,-65.37088 -85.32176,-57.40659 z m 2.80071,29.231473 5.1e-4,-9.9e-5 c 2.13365,-0.334266 3.95652,0.01931 5.31987,1.031879 4.77648,3.547266 2.89647,14.166887 -4.19904,23.719127 -7.09532,9.55196 -16.7189,14.41932 -21.49476,10.87151 -4.77606,-3.54747 -2.89605,-14.166665 4.19913,-23.718615 4.83297,-6.506353 11.08277,-11.106027 16.17429,-11.903802 z m 47.56936,23.874148 c 2.13386,-0.334401 3.95691,0.01915 5.32037,1.031789 4.77577,3.54775 2.89554,14.16692 -4.19964,23.7187 -7.09514,9.55189 -16.7186,14.41945 -21.49466,10.87203 -4.77578,-3.54775 -2.89554,-14.16693 4.19965,-23.71872 4.83296,-6.50635 11.08277,-11.10602 16.17428,-11.903799 z M 151.12801,132.2755 c 2.17877,-0.55401 4.13861,-0.53197 5.77959,0.065 7.31131,2.65972 6.76636,15.88363 -1.21719,29.5365 -7.98356,13.65282 -20.38249,22.56458 -27.69389,19.90504 -7.31132,-2.65972 -6.76637,-15.88364 1.21719,-29.53651 5.96208,-10.19594 14.66479,-18.12654 21.9143,-19.97007 z"
id="path897"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccsssccccccscccccccccccccccc" />
<ellipse
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="path962"
cx="128.25729"
cy="26.431171"
rx="17.575893"
ry="21.733631"
transform="rotate(16.845913)" />
<ellipse
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29560357;stroke-opacity:1"
id="path964"
cx="245.22121"
cy="-179.49768"
rx="18.099041"
ry="22.821976"
transform="matrix(0.33166949,0.94339565,-0.88087771,0.47334392,0,0)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="flaschengeist-logo-white-with-kontur.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="427.27412"
inkscape:cy="505.5186"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<g
id="g875"
transform="matrix(0.04643093,-0.35277275,0.35277275,0.04643093,7.9696963,229.75997)"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
<g
id="g819"
transform="translate(-233.70212,301.68819)"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
<path
id="path817"
d="m 206.836,94.524 c 0,-17.132 0,-94.524 0,-94.524 h -12.723 -7.534 -12.717 c 0,0 0,77.392 0,94.524 0,17.132 -32.207,37.929 -32.207,62.346 0,24.411 0,201.009 0,207.236 0,6.229 4.166,16.615 13.501,16.615 6.355,0 22.012,0 31.435,0 4.45,0 7.534,0 7.534,0 9.423,0 25.073,0 31.429,0 9.342,0 13.513,-10.387 13.513,-16.615 0,-6.228 0,-182.826 0,-207.236 -0.001,-24.417 -32.231,-45.203 -32.231,-62.346 z m -25.857,27.757 c -2.085,2.847 -4.415,6.437 -6.947,10.579 -2.562,4.153 -4.99,8.993 -7.459,14.244 -0.941,2.684 -1.906,5.496 -2.713,8.313 -0.029,2.986 -0.976,5.188 -0.563,8.935 0.25,6.948 0.523,14.256 0.801,21.82 0.5,15.133 0.593,31.284 0.721,47.422 -0.035,32.287 -1.011,64.588 -2.521,88.801 -0.662,12.107 -1.748,22.191 -2.26,29.256 -0.627,7.064 -1.15,11.107 -1.15,11.107 0,0 -0.528,-4.043 -1.156,-11.107 -0.511,-7.064 -1.598,-17.148 -2.26,-29.256 -1.51,-24.213 -2.487,-56.514 -2.521,-88.801 0.116,-16.139 0.209,-32.289 0.72,-47.422 0.279,-7.563 0.552,-14.872 0.802,-21.82 -0.122,-3.224 1.092,-7.604 1.784,-11.09 1.342,-3.282 2.829,-6.327 4.247,-9.196 1.726,-2.638 3.358,-5.165 4.926,-7.564 1.784,-2.19 3.474,-4.258 5.054,-6.222 3.288,-3.788 6.21,-6.989 8.761,-9.487 5.042,-5.042 8.278,-7.517 8.278,-7.517 0,0 -2.524,3.224 -6.544,9.005 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
</g>
<g
id="g821"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g823"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g825"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g827"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g829"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g831"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g833"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g835"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g837"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g839"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g841"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g843"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g845"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g847"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
<g
id="g849"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-opacity:1">
</g>
</g>
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#0c0000;stroke-width:1.66533339;stroke-opacity:1"
d="M 440.92969 56.667969 A 66.428573 82.142858 16.845913 0 0 371.42188 118.32031 A 66.428573 82.142858 16.845913 0 0 411.19531 216.18945 A 66.428573 82.142858 16.845913 0 0 417.75781 217.64648 C 381.35143 302.41695 370.46475 410.50548 348.14648 499.98438 C 297.07737 687.66963 128.47878 622.66455 146.45508 716.38281 C 162.50962 800.08215 408.23439 883.99329 418.23438 885.42188 C 446.35062 889.43845 218.75133 784.15526 265.40039 726.04102 C 306.49074 674.8517 472.92361 721.17976 650.12695 656.71875 C 726.85809 628.34499 797.67407 580.21865 845.90625 519.00977 A 66.376657 87.827349 48.964508 0 0 927.6875 519.24805 A 66.376657 87.827349 48.964508 0 0 980.98047 413.88477 A 66.376657 87.827349 48.964508 0 0 907.36328 380.67383 C 911.18715 353.20036 910.57006 324.58905 904.71094 295.1582 C 874.19541 141.87518 734.80037 48.0882 582.23438 78.189453 C 572.33148 80.143182 562.48437 82.604632 552.73047 85.566406 C 533.33782 91.455119 516.21653 99.676893 501.02148 109.8418 A 66.428573 82.142858 16.845913 0 0 458.80469 58.953125 A 66.428573 82.142858 16.845913 0 0 440.92969 56.667969 z M 598.64062 188.20703 C 604.22194 188.22929 609.06312 189.70004 612.92773 192.57031 C 630.98057 205.9773 623.87627 246.11384 597.05859 282.2168 C 570.24164 318.31869 533.86885 336.71569 515.81836 323.30664 C 497.76711 309.89888 504.87302 269.76201 531.68945 233.66016 C 549.9558 209.06922 573.57677 191.68513 592.82031 188.66992 L 592.82227 188.66992 C 594.83831 188.35408 596.78019 188.19961 598.64062 188.20703 z M 778.42969 278.44141 C 784.01155 278.46343 788.85382 279.93226 792.71875 282.80273 C 810.7689 296.21155 803.66213 336.34605 776.8457 372.44727 C 750.02943 408.5489 713.65672 426.94663 695.60547 413.53906 C 677.55528 400.13024 684.66205 359.99578 711.47852 323.89453 C 729.74482 299.3036 753.36587 281.91757 772.60938 278.90234 C 774.62562 278.58637 776.56907 278.43406 778.42969 278.44141 z M 582.87695 399.91016 C 586.53338 399.95128 589.93604 400.53593 593.03711 401.66406 C 620.67041 411.71655 618.60959 461.69743 588.43555 513.29883 C 558.26146 564.90004 511.39926 598.58305 483.76562 588.53125 C 456.13229 578.47876 458.1931 528.49788 488.36719 476.89648 C 510.90103 438.36065 543.79364 408.38759 571.19336 401.41992 C 575.31072 400.37297 579.22053 399.86903 582.87695 399.91016 z "
transform="matrix(0.26458333,0,0,0.26458333,0,26.06665)"
id="path897" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="flaschengeist-logo-white.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="427.27412"
inkscape:cy="505.5186"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<g
id="g875"
transform="matrix(0.04643093,-0.35277275,0.35277275,0.04643093,7.9696963,229.75997)"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
<g
id="g819"
transform="translate(-233.70212,301.68819)"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
<path
id="path817"
d="m 206.836,94.524 c 0,-17.132 0,-94.524 0,-94.524 h -12.723 -7.534 -12.717 c 0,0 0,77.392 0,94.524 0,17.132 -32.207,37.929 -32.207,62.346 0,24.411 0,201.009 0,207.236 0,6.229 4.166,16.615 13.501,16.615 6.355,0 22.012,0 31.435,0 4.45,0 7.534,0 7.534,0 9.423,0 25.073,0 31.429,0 9.342,0 13.513,-10.387 13.513,-16.615 0,-6.228 0,-182.826 0,-207.236 -0.001,-24.417 -32.231,-45.203 -32.231,-62.346 z m -25.857,27.757 c -2.085,2.847 -4.415,6.437 -6.947,10.579 -2.562,4.153 -4.99,8.993 -7.459,14.244 -0.941,2.684 -1.906,5.496 -2.713,8.313 -0.029,2.986 -0.976,5.188 -0.563,8.935 0.25,6.948 0.523,14.256 0.801,21.82 0.5,15.133 0.593,31.284 0.721,47.422 -0.035,32.287 -1.011,64.588 -2.521,88.801 -0.662,12.107 -1.748,22.191 -2.26,29.256 -0.627,7.064 -1.15,11.107 -1.15,11.107 0,0 -0.528,-4.043 -1.156,-11.107 -0.511,-7.064 -1.598,-17.148 -2.26,-29.256 -1.51,-24.213 -2.487,-56.514 -2.521,-88.801 0.116,-16.139 0.209,-32.289 0.72,-47.422 0.279,-7.563 0.552,-14.872 0.802,-21.82 -0.122,-3.224 1.092,-7.604 1.784,-11.09 1.342,-3.282 2.829,-6.327 4.247,-9.196 1.726,-2.638 3.358,-5.165 4.926,-7.564 1.784,-2.19 3.474,-4.258 5.054,-6.222 3.288,-3.788 6.21,-6.989 8.761,-9.487 5.042,-5.042 8.278,-7.517 8.278,-7.517 0,0 -2.524,3.224 -6.544,9.005 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
<g
id="g821"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g823"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g825"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g827"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g829"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g831"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g833"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g835"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g837"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g839"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g841"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g843"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g845"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g847"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
<g
id="g849"
style="fill:#ffffff;fill-opacity:1;stroke:#0000d2;stroke-opacity:1">
</g>
</g>
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.44061947;stroke-opacity:1"
d="m 154.04963,46.75413 c -2.62014,0.516924 -5.22545,1.168424 -7.80617,1.95206 -42.75163,12.981828 -43.91253,68.68501 -54.129684,109.64785 -13.512037,49.65839 -58.120549,32.45922 -53.364321,57.25551 4.247764,22.14545 69.262455,44.34715 71.908285,44.72513 7.43909,1.06272 -52.780019,-26.79368 -40.437456,-42.16974 10.871821,-13.54384 54.907216,-1.28617 101.792266,-18.34148 41.23972,-15.24969 76.0405,-52.05406 67.35884,-95.66274 -8.0739,-40.556134 -44.95534,-65.37088 -85.32176,-57.40659 z m 2.80071,29.231473 5.1e-4,-9.9e-5 c 2.13365,-0.334266 3.95652,0.01931 5.31987,1.031879 4.77648,3.547266 2.89647,14.166887 -4.19904,23.719127 -7.09532,9.55196 -16.7189,14.41932 -21.49476,10.87151 -4.77606,-3.54747 -2.89605,-14.166665 4.19913,-23.718615 4.83297,-6.506353 11.08277,-11.106027 16.17429,-11.903802 z m 47.56936,23.874148 c 2.13386,-0.334401 3.95691,0.01915 5.32037,1.031789 4.77577,3.54775 2.89554,14.16692 -4.19964,23.7187 -7.09514,9.55189 -16.7186,14.41945 -21.49466,10.87203 -4.77578,-3.54775 -2.89554,-14.16693 4.19965,-23.71872 4.83296,-6.50635 11.08277,-11.10602 16.17428,-11.903799 z M 151.12801,132.2755 c 2.17877,-0.55401 4.13861,-0.53197 5.77959,0.065 7.31131,2.65972 6.76636,15.88363 -1.21719,29.5365 -7.98356,13.65282 -20.38249,22.56458 -27.69389,19.90504 -7.31132,-2.65972 -6.76637,-15.88364 1.21719,-29.53651 5.96208,-10.19594 14.66479,-18.12654 21.9143,-19.97007 z"
id="path897"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccsssccccccscccccccccccccccc" />
<ellipse
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="path962"
cx="128.25729"
cy="26.431171"
rx="17.575893"
ry="21.733631"
transform="rotate(16.845913)" />
<ellipse
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29560357;stroke-opacity:1"
id="path964"
cx="245.22121"
cy="-179.49768"
rx="18.099041"
ry="22.821976"
transform="matrix(0.33166949,0.94339565,-0.88087771,0.47334392,0,0)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="flaschengeist-logo.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="152.98841"
inkscape:cy="505.5186"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1015"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<circle
id="path10"
cx="135.46666"
cy="161.53333"
style="stroke-width:0.26506463;fill:#1976d2;fill-opacity:1"
r="135.46666" />
<g
id="g875"
transform="matrix(0.04643093,-0.35277275,0.35277275,0.04643093,7.9696963,229.75997)"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1">
<g
id="g819"
transform="translate(-233.70212,301.68819)"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1">
<path
id="path817"
d="m 206.836,94.524 c 0,-17.132 0,-94.524 0,-94.524 h -12.723 -7.534 -12.717 c 0,0 0,77.392 0,94.524 0,17.132 -32.207,37.929 -32.207,62.346 0,24.411 0,201.009 0,207.236 0,6.229 4.166,16.615 13.501,16.615 6.355,0 22.012,0 31.435,0 4.45,0 7.534,0 7.534,0 9.423,0 25.073,0 31.429,0 9.342,0 13.513,-10.387 13.513,-16.615 0,-6.228 0,-182.826 0,-207.236 -0.001,-24.417 -32.231,-45.203 -32.231,-62.346 z m -25.857,27.757 c -2.085,2.847 -4.415,6.437 -6.947,10.579 -2.562,4.153 -4.99,8.993 -7.459,14.244 -0.941,2.684 -1.906,5.496 -2.713,8.313 -0.029,2.986 -0.976,5.188 -0.563,8.935 0.25,6.948 0.523,14.256 0.801,21.82 0.5,15.133 0.593,31.284 0.721,47.422 -0.035,32.287 -1.011,64.588 -2.521,88.801 -0.662,12.107 -1.748,22.191 -2.26,29.256 -0.627,7.064 -1.15,11.107 -1.15,11.107 0,0 -0.528,-4.043 -1.156,-11.107 -0.511,-7.064 -1.598,-17.148 -2.26,-29.256 -1.51,-24.213 -2.487,-56.514 -2.521,-88.801 0.116,-16.139 0.209,-32.289 0.72,-47.422 0.279,-7.563 0.552,-14.872 0.802,-21.82 -0.122,-3.224 1.092,-7.604 1.784,-11.09 1.342,-3.282 2.829,-6.327 4.247,-9.196 1.726,-2.638 3.358,-5.165 4.926,-7.564 1.784,-2.19 3.474,-4.258 5.054,-6.222 3.288,-3.788 6.21,-6.989 8.761,-9.487 5.042,-5.042 8.278,-7.517 8.278,-7.517 0,0 -2.524,3.224 -6.544,9.005 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
</g>
<g
id="g821"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g823"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g825"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g827"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g829"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g831"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g833"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g835"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g837"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g839"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g841"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g843"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g845"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g847"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
<g
id="g849"
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-opacity:1" />
</g>
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.44061947;stroke-opacity:1"
d="m 154.04963,46.75413 c -2.62014,0.516924 -5.22545,1.168424 -7.80617,1.95206 -42.75163,12.981828 -43.91253,68.68501 -54.129684,109.64785 -13.512037,49.65839 -58.120549,32.45922 -53.364321,57.25551 4.247764,22.14545 69.262455,44.34715 71.908285,44.72513 7.43909,1.06272 -52.780019,-26.79368 -40.437456,-42.16974 10.871821,-13.54384 54.907216,-1.28617 101.792266,-18.34148 41.23972,-15.24969 76.0405,-52.05406 67.35884,-95.66274 -8.0739,-40.556134 -44.95534,-65.37088 -85.32176,-57.40659 z m 2.80071,29.231473 5.1e-4,-9.9e-5 c 2.13365,-0.334266 3.95652,0.01931 5.31987,1.031879 4.77648,3.547266 2.89647,14.166887 -4.19904,23.719127 -7.09532,9.55196 -16.7189,14.41932 -21.49476,10.87151 -4.77606,-3.54747 -2.89605,-14.166665 4.19913,-23.718615 4.83297,-6.506353 11.08277,-11.106027 16.17429,-11.903802 z m 47.56936,23.874148 c 2.13386,-0.334401 3.95691,0.01915 5.32037,1.031789 4.77577,3.54775 2.89554,14.16692 -4.19964,23.7187 -7.09514,9.55189 -16.7186,14.41945 -21.49466,10.87203 -4.77578,-3.54775 -2.89554,-14.16693 4.19965,-23.71872 4.83296,-6.50635 11.08277,-11.10602 16.17428,-11.903799 z M 151.12801,132.2755 c 2.17877,-0.55401 4.13861,-0.53197 5.77959,0.065 7.31131,2.65972 6.76636,15.88363 -1.21719,29.5365 -7.98356,13.65282 -20.38249,22.56458 -27.69389,19.90504 -7.31132,-2.65972 -6.76637,-15.88364 1.21719,-29.53651 5.96208,-10.19594 14.66479,-18.12654 21.9143,-19.97007 z"
id="path897"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccsssccccccscccccccccccccccc" />
<ellipse
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332;stroke-opacity:1"
id="path962"
cx="128.25729"
cy="26.431171"
rx="17.575893"
ry="21.733631"
transform="rotate(16.845913)" />
<ellipse
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.29560357;stroke-opacity:1"
id="path964"
cx="245.22121"
cy="-179.49768"
rx="18.099041"
ry="22.821976"
transform="matrix(0.33166949,0.94339565,-0.88087771,0.47334392,0,0)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -15,10 +15,62 @@
<allow-intent href="geo:*" />
<platform name="android">
<allow-intent href="market:*" />
<icon density="ldpi" src="res/android/ldpi.png" />
<icon density="mdpi" src="res/android/mdpi.png" />
<icon density="hdpi" src="res/android/hdpi.png" />
<icon density="xhdpi" src="res/android/xhdpi.png" />
<icon density="xxhdpi" src="res/android/xxhdpi.png" />
<icon density="xxxhdpi" src="res/android/xxxhdpi.png" />
<splash density="land-ldpi" src="res/screen/android/splash-land-ldpi.png" />
<splash density="port-ldpi" src="res/screen/android/splash-port-ldpi.png" />
<splash density="land-mdpi" src="res/screen/android/splash-land-mdpi.png" />
<splash density="port-mdpi" src="res/screen/android/splash-port-mdpi.png" />
<splash density="land-hdpi" src="res/screen/android/splash-land-hdpi.png" />
<splash density="port-hdpi" src="res/screen/android/splash-port-hdpi.png" />
<splash density="land-xhdpi" src="res/screen/android/splash-land-xhdpi.png" />
<splash density="port-xhdpi" src="res/screen/android/splash-port-xhdpi.png" />
<splash density="land-xxhdpi" src="res/screen/android/splash-land-xxhdpi.png" />
<splash density="port-xxhdpi" src="res/screen/android/splash-port-xxhdpi.png" />
<splash density="land-xxxhdpi" src="res/screen/android/splash-land-xxxhdpi.png" />
<splash density="port-xxxhdpi" src="res/screen/android/splash-port-xxxhdpi.png" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
<icon height="57" src="res/ios/icon.png" width="57" />
<icon height="114" src="res/ios/icon@2x.png" width="114" />
<icon height="40" src="res/ios/icon-20@2x.png" width="40" />
<icon height="60" src="res/ios/icon-20@3x.png" width="60" />
<icon height="29" src="res/ios/icon-29.png" width="29" />
<icon height="58" src="res/ios/icon-29@2x.png" width="58" />
<icon height="87" src="res/ios/icon-29@3x.png" width="87" />
<icon height="80" src="res/ios/icon-40@2x.png" width="80" />
<icon height="120" src="res/ios/icon-60@2x.png" width="120" />
<icon height="180" src="res/ios/icon-60@3x.png" width="180" />
<icon height="20" src="res/ios/icon-20.png" width="20" />
<icon height="40" src="res/ios/icon-40.png" width="40" />
<icon height="50" src="res/ios/icon-50.png" width="50" />
<icon height="100" src="res/ios/icon-50@2x.png" width="100" />
<icon height="72" src="res/ios/icon-72.png" width="72" />
<icon height="144" src="res/ios/icon-72@2x.png" width="144" />
<icon height="76" src="res/ios/icon-76.png" width="76" />
<icon height="152" src="res/ios/icon-76@2x.png" width="152" />
<icon height="167" src="res/ios/icon-83.5@2x.png" width="167" />
<icon height="1024" src="res/ios/icon-1024.png" width="1024" />
<icon height="48" src="res/ios/icon-24@2x.png" width="48" />
<icon height="55" src="res/ios/icon-27.5@2x.png" width="55" />
<icon height="88" src="res/ios/icon-44@2x.png" width="88" />
<icon height="172" src="res/ios/icon-86@2x.png" width="172" />
<icon height="196" src="res/ios/icon-98@2x.png" width="196" />
<splash src="res/screen/ios/Default@2x~iphone~anyany.png" />
<splash src="res/screen/ios/Default@2x~iphone~comany.png" />
<splash src="res/screen/ios/Default@2x~iphone~comcom.png" />
<splash src="res/screen/ios/Default@3x~iphone~anyany.png" />
<splash src="res/screen/ios/Default@3x~iphone~anycom.png" />
<splash src="res/screen/ios/Default@3x~iphone~comany.png" />
<splash src="res/screen/ios/Default@2x~ipad~anyany.png" />
<splash src="res/screen/ios/Default@2x~ipad~comany.png" />
</platform>
<allow-navigation href="about:*" />
<preference name="SplashMaintainAspectRatio" value="true" />
</widget>

File diff suppressed because it is too large Load Diff

View File

@ -13,15 +13,19 @@
"author": "Apache Cordova Team",
"license": "Apache-2.0",
"devDependencies": {
"cordova-android": "^9.0.0",
"cordova-ios": "^6.1.1",
"cordova-plugin-splashscreen": "^6.0.0",
"cordova-plugin-whitelist": "^1.3.4"
},
"cordova": {
"plugins": {
"cordova-plugin-whitelist": {}
"cordova-plugin-whitelist": {},
"cordova-plugin-splashscreen": {}
},
"platforms": [
"ios"
"ios",
"android"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -3,6 +3,8 @@ import { boot } from 'quasar/wrappers';
import config from '../config';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
import { LocalStorage } from 'quasar';
import { Notify } from 'quasar';
declare module 'vue/types/vue' {
interface Vue {
@ -10,11 +12,29 @@ declare module 'vue/types/vue' {
}
}
export const setBaseUrl = (url: string) => {
LocalStorage.set('baseURL', url);
axios.defaults.baseURL = url;
Notify.create({
message: 'Serveraddresse gespeichert',
position: 'bottom',
caption: `${url}`,
color: 'positive'
});
setTimeout(() => {
window.location.reload();
}, 5000);
};
export default boot<Store<StateInterface>>(({ Vue, store, router }) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Vue.prototype.$axios = axios;
const baseURL = <string | undefined>LocalStorage.getItem('baseURL');
if (baseURL) {
axios.defaults.baseURL = baseURL;
} else {
axios.defaults.baseURL = config.baseURL;
}
/***
* Intercept requests and insert Token if available
*/

View File

@ -43,7 +43,7 @@ interface Props {
label?: string;
readonly: boolean;
type: string;
rules: Array<any>;
rules: Array<string>;
}
export default defineComponent({
@ -63,7 +63,7 @@ export default defineComponent({
}
},
rules: {
default: []
default: () => { return [] }
}
},
setup(props: Props, { emit }: { emit: any }) {

View File

@ -27,10 +27,11 @@ declare namespace FG {
id: number;
time: Date;
amount: number;
reversal?: this;
reversal_id: number;
sender_id?: string;
receiver_id?: string;
author_id?: string;
original_id?: number;
}
interface Event {
id: number;

View File

@ -14,8 +14,8 @@
<q-toolbar-title>
<router-link :to="{ name: 'dashboard' }" style="text-decoration: none; color: inherit">
<q-avatar>
<img src="logo.svg" />
<q-avatar rounded>
<img src="flaschengeist-logo-white.svg" />
</q-avatar>
<span class="gt-xs"> Flaschengeist </span>
</router-link>

View File

@ -3,8 +3,8 @@
<q-header elevated>
<q-toolbar>
<q-toolbar-title>
<q-avatar>
<img src="logo.svg" />
<q-avatar rounded>
<img src="flaschengeist-logo-white.svg" />
</q-avatar>
<span class="gt-xs">
Flaschengeist

View File

@ -36,6 +36,21 @@
</div>
</q-form>
</q-card-section>
<div class="row justify-end">
<q-btn flat round icon="mdi-menu-down" class="cordova-only" @click="openServerSettings" />
</div>
<q-slide-transition class="cordova-only">
<div v-show="visible">
<q-separator />
<q-card-section>
<q-form ref="ServerSettingsForm" @submit="changeUrl" class="q-gutter-md">
<div class="text-h6">Servereinstellung</div>
<q-input filled label="Server" dense v-model="server" />
<q-btn size="xs" dense color="primary" label="Speichern" type="submit" />
</q-form>
</q-card-section>
</div>
</q-slide-transition>
</q-card>
</q-page>
</template>
@ -43,6 +58,7 @@
<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';
import { Loading, Notify } from 'quasar';
import { setBaseUrl } from 'boot/axios';
export default defineComponent({
// name: 'PageName'
@ -53,6 +69,18 @@ export default defineComponent({
const userid = ref('');
const password = ref('');
const rules = [(val: string) => (val && val.length > 0) || 'Feld darf nicht leer sein!'];
const server = ref<string | undefined>(root.$axios.defaults.baseURL);
const visible = ref(false);
function openServerSettings() {
visible.value = !visible.value;
}
function changeUrl() {
if (server.value) {
setBaseUrl(server.value);
}
}
function doLogin() {
Loading.show({
@ -116,7 +144,17 @@ export default defineComponent({
});
}
return { userid, password, doLogin, doReset, rules };
return {
userid,
password,
doLogin,
doReset,
rules,
server,
changeUrl,
visible,
openServerSettings
};
}
});
</script>

View File

@ -6,7 +6,7 @@
>
<div class="fit row justify-center content-center items-center">
<q-img
:src="$q.dark.isActive ? 'logo.svg' : 'logo-dark.svg'"
:src="$q.dark.isActive ? 'flaschengeist-logo.svg' : 'flaschengeist-logo.svg'"
class="col-12 q-ma-md"
style="min-width: 200px; max-width: 400px"
/>

View File

@ -0,0 +1,125 @@
<template>
<q-card>
<BalanceHeader @update:user="userUpdated" :showSelector="showSelector" @open-history="openHistory"/>
<q-separator />
<q-card-section class="row q-col-gutter-md" v-if="shortCuts">
<div :key="index" v-for="(shortcut, index) in shortCuts" class="col-4">
<q-btn
push
v-if="shortcut"
color="primary"
style="width: 100%"
:label="shortcut.toFixed(2).toString() + ' €'"
@click="changeBalance(shortcut)"
>
<q-popup-proxy context-menu>
<q-btn label="Entfernen" @click="removeShortcut(shortcut)" />
</q-popup-proxy>
<q-tooltip>Rechtsklick um Verknüpfung zu entfernen</q-tooltip>
</q-btn>
</div></q-card-section
>
<q-card-section class="row q-col-gutter-md items-center">
<div class="col-sm-4 col-xs-12">
<q-input
v-model.number="amount"
type="number"
filled
label="Eigener Betrag"
step="0.1"
min="0"
/>
</div>
<div class="col-sm-4 col-xs-6">
<q-btn
style="width: 100%"
color="primary"
label="Anschreiben"
@click="changeBalance(amount * -1)"
><q-tooltip>Rechtsklick um Betrag als Verknüpfung hinzuzufügen</q-tooltip>
<q-popup-proxy context-menu v-model="showAddShortcut">
<q-btn label="neue Verknüpfung" @click="addShortcut"></q-btn>
</q-popup-proxy>
</q-btn>
</div>
<div class="col-sm-4 col-xs-6">
<q-btn
v-if="canAddCredit"
style="width: 100%"
color="secondary"
label="Gutschreiben"
@click="changeBalance(amount)"
/>
</div>
</q-card-section>
</q-card>
</template>
<script lang="ts">
import { computed, ref, defineComponent, onBeforeMount } from '@vue/composition-api';
import { hasPermission } from 'src/utils/permission';
import { StateInterfaceBalance } from '../store/balance';
import { Store } from 'vuex';
import BalanceHeader from '../components/BalanceHeader.vue';
import PERMISSIONS from '../permissions';
export default defineComponent({
name: 'BalanceAdd',
components: { BalanceHeader },
setup(_, { root, emit }) {
onBeforeMount(() => {
void store.dispatch('balance/getShortcuts');
if (store.state.balance.transactions.length == 0)
// No transaction, load at most six since yesterday
void store.dispatch('balance/getTransactions', {
filter: { limit: 6, from: new Date(new Date().setDate(new Date().getDate() - 1)) }
});
});
const store = <Store<StateInterfaceBalance>>root.$store;
const amount = ref<number>(0);
const showAddShortcut = ref(false);
const user = ref(store.state.user.currentUser);
const shortCuts = ref(store.state.balance.shortcuts);
const canAddCredit = computed(() => hasPermission(PERMISSIONS.CREDIT, store));
const showSelector = computed(
() => hasPermission(PERMISSIONS.DEBIT, store) || hasPermission(PERMISSIONS.CREDIT, store)
);
function addShortcut() {
if (amount.value != 0) void store.dispatch('balance/addShortcut', amount.value * -1);
}
function removeShortcut(shortcut: number) {
void store.dispatch('balance/removeShortcut', shortcut);
}
function userUpdated(selectedUser: FG.User) {
user.value = selectedUser;
}
function changeBalance(amount: number) {
store
.dispatch('balance/changeBalance', { amount: amount, user: user.value?.userid })
.catch(err => console.log(err));
}
function openHistory() {
emit('open-history')
}
return {
user,
addShortcut,
canAddCredit,
removeShortcut,
showAddShortcut,
changeBalance,
amount,
showSelector,
shortCuts,
userUpdated,
openHistory
};
}
});
</script>

View File

@ -1,12 +1,15 @@
<template>
<q-card-section class="fit row justify-left content-center items-center q-col-gutter-sm">
<div class="text-h6 col-6">
<div class="text-h6 col-5">
Aktueller Stand: {{ balance.balance.toFixed(2) }}
<q-badge color="negative" align="top" v-if="isLocked"> gesperrt </q-badge>
</div>
<div v-if="showSelector" class="col-6">
<UserSelector :user="user" @update:user="userUpdated" />
</div>
<div class="col-1 justify-end">
<q-btn round flat icon="mdi-format-list-checks" @click="openHistory" />
</div>
</q-card-section>
</template>
@ -43,7 +46,11 @@ export default defineComponent({
emit('update:user', selectedUser);
}
return { user, balance, isLocked, userUpdated };
function openHistory() {
emit('open-history');
}
return { user, balance, isLocked, userUpdated, openHistory };
}
});
</script>

View File

@ -1,19 +1,10 @@
<template>
<q-page padding class="fit row justify-left q-col-gutter-sm">
<div class="col-12">
<q-card>
<BalanceHeader @update:user="senderUpdated" :showSelector="showSelector" />
<BalanceHeader @update:user="senderUpdated" :showSelector="showSelector" @open-history="openHistory"/>
<q-separator />
<q-card-section class="row q-col-gutter-md items-center">
<div class="col-sm-4 col-xs-12">
<q-input
v-model.number="amount"
type="number"
filled
label="Betrag"
step="0.1"
min="0"
/>
<q-input v-model.number="amount" type="number" filled label="Betrag" step="0.1" min="0" />
</div>
<div class="col-sm-4 col-xs-6">
<UserSelector :user="receiver" @update:user="receiverUpdated" label="Empfänger" />
@ -29,11 +20,6 @@
</div>
</q-card-section>
</q-card>
</div>
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-sm-4 col-xs-6">
<Transaction :transaction.sync="transactions[index]" />
</div>
</q-page>
</template>
<script lang="ts">
@ -42,21 +28,19 @@ import { hasPermission } from 'src/utils/permission';
import { StateInterfaceBalance } from '../store/balance';
import { Store } from 'vuex';
import UserSelector from 'src/plugins/user/components/UserSelector.vue';
import Transaction from '../components/Transaction.vue';
import BalanceHeader from '../components/BalanceHeader.vue';
import PERMISSIONS from '../permissions';
export default defineComponent({
name: 'BalanceTransfer',
components: { Transaction, BalanceHeader, UserSelector },
setup(_, { root }) {
components: { BalanceHeader, UserSelector },
setup(_, { root, emit }) {
const store: Store<StateInterfaceBalance> = <Store<StateInterfaceBalance>>root.$store;
const showSelector = computed(() => hasPermission(PERMISSIONS.SEND_OTHER, store));
const sender = ref(store.state.user.currentUser);
const receiver = ref<FG.User | undefined>(undefined);
const amount = ref<number>(0);
const transactions = computed(() => store.state.balance.transactions.slice().reverse());
const sendDisabled = computed(() => {
return !(
@ -85,16 +69,20 @@ export default defineComponent({
.catch(err => console.log(err));
}
function openHistory() {
emit('open-history');
}
return {
sender,
receiver,
amount,
sendAmount,
transactions,
showSelector,
senderUpdated,
receiverUpdated,
sendDisabled
sendDisabled,
openHistory
};
}
});

View File

@ -1,29 +1,37 @@
<template>
<q-card v-bind:class="{ 'bg-grey': isReversed }">
<q-card-section class="row items-start justify-between">
<div class="col text-center">
<div>
<q-card flat square>
<q-card-section class="row items-center justify-between" horizontal>
<div class="col-5 text-left q-px-sm">
<div
v-bind:class="{ 'text-negative': isNegative() }"
class="text-weight-bold"
style="font-size: 2em"
class="text-weight-bold text-h6"
>
<span v-if="isNegative()">-</span>{{ transaction.amount.toFixed(2) }}&#8239;
</div>
<div>{{ text }}</div>
<div>{{ timeStr }}</div>
<div class="text-caption">{{ text }}</div>
<div class="text-caption">{{ timeStr }}</div>
</div>
<div class="col" style="text-align: right">
<div class="col-5 q-px-sm text-center">
<div class="text-subtitle1" v-if="isReversed">
Storniert
</div>
</div>
<div class="col-2 q-pr-sm" style="text-align: right">
<q-btn
color="negative"
:color="isReversed ? 'positive' : 'negative'"
aria-label="Reverse transaction"
icon="mdi-trash-can"
square
:icon="!isReversed ? 'mdi-trash-can' : 'mdi-check-bold'"
size="sm"
round
:disable="!canReverse"
@click="reverse"
/>
</div>
</q-card-section>
</q-card>
<q-separator />
</div>
</template>
<script lang="ts">
@ -77,7 +85,9 @@ export default defineComponent({
}
};
const isReversed = computed(() => props.transaction.reversal != undefined);
const isReversed = computed(
() => props.transaction.reversal_id != undefined || props.transaction.original_id != undefined
);
const canReverse = computed(
() =>

View File

@ -1,121 +0,0 @@
<template>
<q-page padding class="fit row justify-left q-col-gutter-sm">
<div class="col-12">
<q-card>
<BalanceHeader @update:user="userUpdated" :showSelector="showSelector" />
<q-separator />
<q-card-section class="row q-col-gutter-md" v-if="shortCuts">
<div :key="index" v-for="(shortcut, index) in shortCuts" class="col-4">
<q-btn
push
v-if="shortcut"
color="primary"
style="width: 100%"
:label="shortcut.toFixed(2).toString() + ' €'"
@click="changeBalance(shortcut)"
>
<q-popup-proxy context-menu>
<q-btn label="Entfernen" @click="removeShortcut(shortcut)" />
</q-popup-proxy>
<q-tooltip>Rechtsklick um Verknüpfung zu entfernen</q-tooltip>
</q-btn>
</div></q-card-section
>
<q-card-section class="row q-col-gutter-md items-center">
<div class="col-sm-4 col-xs-12">
<q-input
v-model.number="amount"
type="number"
filled
label="Eigener Betrag"
step="0.1"
min="0"
/>
</div>
<div class="col-sm-4 col-xs-6">
<q-btn
style="width: 100%"
color="primary"
label="Anschreiben"
@click="changeBalance(amount * -1)"
><q-tooltip>Rechtsklick um Betrag als Verknüpfung hinzuzufügen</q-tooltip>
<q-popup-proxy context-menu v-model="showAddShortcut">
<q-btn label="neue Verknüpfung" @click="addShortcut"></q-btn>
</q-popup-proxy>
</q-btn>
</div>
<div class="col-sm-4 col-xs-6">
<q-btn
style="width: 100%"
color="secondary"
label="Gutschreiben"
@click="changeBalance(amount)"
/>
</div>
</q-card-section>
</q-card>
</div>
<div v-for="(transaction, index) in transactions" v-bind:key="index" class="col-md-4 col-sm-6">
<!-- TODO: In Vue3 use v-model:transaction="..." -->
<Transaction :transaction.sync="transactions[index]" />
</div>
</q-page>
</template>
<script lang="ts">
import { computed, ref, defineComponent, onBeforeMount } from '@vue/composition-api';
import { hasPermission } from 'src/utils/permission';
import { StateInterfaceBalance } from '../store/balance';
import { Store } from 'vuex';
import Transaction from '../components/Transaction.vue';
import BalanceHeader from '../components/BalanceHeader.vue';
import PERMISSIONS from '../permissions';
export default defineComponent({
name: 'BalanceAdd',
components: { Transaction, BalanceHeader },
setup(_, { root }) {
onBeforeMount(() => void store.dispatch('balance/getShortcuts'));
const store = <Store<StateInterfaceBalance>>root.$store;
const amount = ref<number>(0);
const showAddShortcut = ref(false);
const transactions = computed(() => store.state.balance.transactions.slice().reverse());
const user = ref(store.state.user.currentUser);
const shortCuts = ref(store.state.balance.shortcuts);
const showSelector = computed(
() => hasPermission(PERMISSIONS.DEBIT, store) || hasPermission(PERMISSIONS.CREDIT, store)
);
function addShortcut() {
if (amount.value != 0) void store.dispatch('balance/addShortcut', amount.value * -1);
}
function removeShortcut(shortcut: number) {
void store.dispatch('balance/removeShortcut', shortcut);
}
function userUpdated(selectedUser: FG.User) {
user.value = selectedUser;
}
function changeBalance(amount: number) {
store
.dispatch('balance/changeBalance', { amount: amount, user: user.value?.userid })
.catch(err => console.log(err));
}
return {
user,
addShortcut,
removeShortcut,
showAddShortcut,
changeBalance,
transactions,
amount,
showSelector,
shortCuts,
userUpdated
};
}
});
</script>

View File

@ -11,25 +11,24 @@
</template>
<script lang="ts">
// TODO: Load all users / balances
// TODO: Fill usefull data
import { computed, defineComponent } from '@vue/composition-api';
import { StateInterfaceBalance } from '../store/balance';
import { ref, defineComponent, onMounted } from '@vue/composition-api';
import { StateInterfaceBalance, BalancesResponse } from '../store/balance';
import { Store } from 'vuex';
export default defineComponent({
// name: 'PageName'
setup(_, { root }) {
const store = <Store<StateInterfaceBalance>>root.$store;
const rows = computed(() => {
const fo: Array<{ userid: string; balance: number }> = [];
store.state.balance.balances.forEach((value, key) =>
fo.push(Object.assign(value, { userid: key }))
onMounted(
() =>
void store
.dispatch('balance/getBalances')
.then((balances: Array<BalancesResponse>) => rows.value.push(...balances))
);
return fo;
});
const rows = ref(new Array<BalancesResponse>());
const columns = [
{
@ -41,16 +40,22 @@ export default defineComponent({
sortable: true
},
{
name: 'balance',
label: 'Kontostand',
field: 'balance',
name: 'credit',
label: 'Haben',
field: 'credit',
format: (val: number) => val.toFixed(2)
},
{
name: 'limit',
label: 'Limit',
field: 'limit',
format: (val: number) => (val === null ? 'keins' : val)
name: 'debit',
label: 'Soll',
field: 'debit',
format: (val: number) => val.toFixed(2)
},
{
name: 'balance',
label: 'Kontostand',
format: (_: undefined, row: { debit: number; credit: number }) =>
(row.credit - row.debit).toFixed(2)
}
];
return { rows, columns };

View File

@ -1,48 +1,118 @@
<template>
<div>
<q-page padding v-if="checkMain">
<q-card>
<q-card-section>
<q-list v-for="(mainRoute, index) in mainRoutes" :key="'mainRoute' + index">
<essential-link
v-for="(route, index2) in mainRoute.children"
:key="'route' + index2"
:title="route.title"
:icon="route.icon"
:link="route.name"
:permissions="route.meta.permissions"
<q-tabs v-model="tab" v-if="$q.screen.gt.sm">
<q-tab
v-for="(tabindex, index) in tabs"
:key="'tab' + index"
:name="tabindex.name"
:label="tabindex.label"
/>
</q-tabs>
<div class="fit row justify-end" v-else>
<q-btn flat round icon="mdi-menu" @click="showDrawer = !showDrawer; show = false" />
</div>
<q-drawer side="right" v-model="showDrawer" @click="showDrawer = !showDrawer" behavior="mobile">
<q-list v-model="tab" v-if="!$q.screen.gt.sm && !show">
<q-item
v-for="(tabindex, index) in tabs"
:key="'tab' + index"
:active="tab == tabindex.name"
clickable
@click="tab = tabindex.name"
>
<q-item-label>{{ tabindex.label }}</q-item-label>
</q-item>
</q-list>
</q-card-section>
</q-card>
<q-list v-if="show">
<div
v-for="(transaction, index) in transactions"
v-bind:key="index"
class="col-sm-12"
>
<!-- TODO: In Vue3 use v-model:transaction="..." -->
<Transaction :transaction.sync="transactions[index]" />
</div>
</q-list>
</q-drawer>
<q-page padding class="fit row justify-left q-col-gutter-sm">
<q-tab-panels
v-model="tab"
style="background-color: transparent"
class="q-pa-none col-12"
animated
>
<q-tab-panel name="add" class="q-px-xs">
<BalanceAdd @open-history="showDrawer = !showDrawer; show = showDrawer"/>
</q-tab-panel>
<q-tab-panel name="transfer" class="q-px-xs">
<BalanceTransfer @open-history="showDrawer = !showDrawer; show = showDrawer"/>
</q-tab-panel>
</q-tab-panels>
</q-page>
<router-view />
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from '@vue/composition-api';
import EssentialLink from 'src/components/navigation/EssentialLink.vue';
import mainRoutes from 'src/plugins/balance/routes';
import { computed, defineComponent, ref, onMounted } from '@vue/composition-api';
import { Store } from 'vuex';
import { BalanceInterface } from 'src/plugins/balance/store/balance';
import setLoadingBar from 'src/utils/loading';
import { StateInterface } from 'src/store';
import { hasPermissions, hasSomePermissions } from 'src/utils/permission';
import PERMISSIONS from '../permissions';
import { Screen } from 'quasar';
import BalanceAdd from '../components/BalanceAdd.vue';
import BalanceTransfer from '../components/BalanceTransfer.vue';
import Transaction from '../components/Transaction.vue';
import { StateInterfaceBalance } from '../store/balance';
export default defineComponent({
// name: 'PageName'
components: { EssentialLink },
name: 'BalanceManage',
components: { BalanceAdd, BalanceTransfer, Transaction },
setup(_, { root }) {
const store = <Store<StateInterface>>root.$store;
const loading = computed(() => {
return (<BalanceInterface>store.state.balance).loading > 0;
});
const checkMain = computed(() => {
return root.$route.matched.length == 2;
const store = <Store<StateInterfaceBalance>>root.$store;
const now = new Date()
onMounted(() => {
void store.dispatch('balance/getTransactions', {filter: {from: new Date(now.getFullYear(), now.getMonth(), now.getDay())}})
})
const transactions = computed(() =>
store.state.balance.transactions
.filter(t => t.original_id == undefined)
.sort((a, b) => (a.time >= b.time ? -1 : 1))
);
const canAdd = () =>
hasSomePermissions([PERMISSIONS.DEBIT, PERMISSIONS.CREDIT, PERMISSIONS.DEBIT_OWN], store);
interface Tab {
name: string;
label: string;
}
const tabs: Tab[] = [
...(canAdd() ? [{ name: 'add', label: 'Anschreiben' }] : []),
...(hasSomePermissions([PERMISSIONS.SEND, PERMISSIONS.SEND_OTHER], store)
? [{ name: 'transfer', label: 'Übertragen' }]
: [])
];
const drawer = ref<boolean>(false);
const showDrawer = computed({
get: () => {
return !Screen.gt.sm && drawer.value;
},
set: (val: boolean) => {
drawer.value = val;
}
});
setLoadingBar(loading);
return { checkMain, mainRoutes };
const tab = ref<string>(canAdd() ? 'add' : 'transfer');
const show = ref<boolean>(false);
return {
showDrawer,
tab,
tabs,
transactions,
show
};
}
});
</script>

View File

@ -0,0 +1,154 @@
<template>
<q-page padding>
<q-card>
<q-card-section class="text-center">
<div class="text-h4">Aktueller Stand</div>
<div style="font-size: 2em">{{ balance.toFixed(2) }}&#8239;</div>
</q-card-section>
<q-separator />
<q-card-section>
<q-table
title="Buchungen"
:data="data"
:columns="columns"
row-key="id"
:pagination.sync="pagination"
:loading="loading"
@request="onRequest"
binary-state-sort
>
<template v-slot:top>
<q-toggle v-model="showCancelled" label="Stornierte einblenden" />
</template>
<template v-slot:body-cell="props">
<q-td :props="props" v-bind:class="{ 'bg-grey': props.row.reversal_id != null }">
{{ props.value }}
</q-td>
</template>
</q-table>
</q-card-section>
</q-card>
</q-page>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, ref } from '@vue/composition-api';
import { StateInterfaceBalance, TransactionsResponse } from '../store/balance';
import { Store } from 'vuex';
import { formatDateTime } from 'src/utils/datetime';
export default defineComponent({
// name: 'PageName'
setup(_, { root }) {
const store = <Store<StateInterfaceBalance>>root.$store;
onMounted(() => {
void store.dispatch('balance/getBalance');
void store.dispatch('user/getUsers').then(() =>
onRequest({
pagination: pagination.value,
filter: undefined
})
);
});
const showCancelled = ref(false);
const data = ref<FG.Transaction[]>([]);
const loading = ref(false);
const pagination = ref({
sortBy: 'time',
descending: false,
page: 1,
rowsPerPage: 3,
rowsNumber: 10
});
interface PaginationInterface {
sortBy: string;
descending: boolean;
page: number;
rowsPerPage: number;
rowsNumber: number;
}
function onRequest(props: { pagination: PaginationInterface; filter?: string }) {
const { page, rowsPerPage, sortBy, descending } = props.pagination;
loading.value = true;
// get all rows if "All" (0) is selected
const fetchCount = rowsPerPage === 0 ? pagination.value.rowsNumber : rowsPerPage;
// calculate starting row of data
const startRow = (page - 1) * rowsPerPage;
store
.dispatch('balance/getTransactions', {
filter: {
offset: startRow,
limit: fetchCount,
showCancelled: showCancelled.value,
showReversals: false
}
})
.then((result: TransactionsResponse) => {
// clear out existing data and add new
data.value.splice(0, data.value.length, ...result.transactions);
// don't forget to update local pagination object
pagination.value.page = page;
pagination.value.rowsPerPage = rowsPerPage;
pagination.value.sortBy = sortBy;
pagination.value.descending = descending;
if (result.count) pagination.value.rowsNumber = result.count;
})
.finally(() => (loading.value = false));
}
const balance = computed(
() =>
store.state.balance.balances.get((<FG.User>store.state.user.currentUser).userid)?.balance ||
NaN
);
const columns = [
{
name: 'time',
label: 'Datum',
field: 'time',
required: true,
sortable: true,
format: (val: Date) => formatDateTime(new Date(val), true, true, true)
},
{
name: 'text',
label: 'Text',
format: (_: undefined, row: FG.Transaction) => {
if (row.sender_id == null) return 'Gutschrift';
else {
if (row.receiver_id == null) return 'Angeschrieben';
else {
if (row.receiver_id === store.state.user.currentUser?.userid) return 'Bekommen von X';
else return 'Gesendet an X';
}
}
}
},
{
name: 'amount',
label: 'Betrag',
field: 'amount',
format: (val: number) => `${val.toFixed(2)}`
},
{
name: 'author_id',
label: 'Benutzer',
field: 'author_id',
format: (val: string) => {
const user = store.state.user.users.filter(x => x.userid == val);
if (user.length > 0) return user[0].display_name;
else return val;
}
}
];
return { data, pagination, onRequest, loading, balance, columns, showCancelled };
}
});
</script>

View File

@ -7,32 +7,32 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
icon: 'mdi-cash-100',
path: 'balance',
name: 'balance',
redirect: { name: 'balance-add' },
redirect: { name: 'balance-view' },
meta: { permissions: ['user'] },
children: [
{
title: 'Anschreiben',
title: 'Übersicht',
icon: 'mdi-cash-plus',
path: 'add',
name: 'balance-add',
shortcut: true,
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] },
component: () => import('../pages/Add.vue')
path: 'overview',
name: 'balance-view',
meta: { permissions: [permissions.SHOW] },
component: () => import('../pages/Overview.vue')
},
{
title: 'Übertragen',
icon: 'mdi-cash-refund',
path: 'transfer',
name: 'balance-transfer',
meta: { permissions: [permissions.SEND] },
component: () => import('../pages/Transfer.vue')
title: 'Buchen',
icon: 'mdi-cash-plus',
path: 'change',
name: 'balance-change',
shortcut: true,
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] },
component: () => import('../pages/MainPage.vue')
},
{
title: 'Verwaltung',
icon: 'mdi-account-cash',
path: 'admin',
name: 'balance-admin',
meta: { permissions: [permissions.DEBIT_OWN, permissions.SHOW] },
meta: { permissions: [permissions.SET_LIMIT, permissions.SHOW_OTHER] },
component: () => import('../pages/Admin.vue')
}
]

View File

@ -1,5 +1,5 @@
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
import { StateInterface } from 'src/store';
import store, { StateInterface } from 'src/store';
import { axios } from 'src/boot/axios';
import { AxiosResponse } from 'axios';
@ -9,6 +9,15 @@ interface BalanceResponse {
debit: number;
}
export interface BalancesResponse extends BalanceResponse {
userid: string;
}
export interface TransactionsResponse {
transactions: Array<FG.Transaction>;
count?: number;
}
export interface UserBalance extends BalanceResponse {
limit: number | null;
}
@ -31,6 +40,10 @@ const state: BalanceInterface = {
loading: 0
};
function fixTransaction(t: FG.Transaction) {
t.time = new Date(t.time);
}
const mutations: MutationTree<BalanceInterface> = {
setBalance(state, data: { userid: string; balance: BalanceResponse }) {
state.balances.set(
@ -59,9 +72,13 @@ const mutations: MutationTree<BalanceInterface> = {
addTransaction(state, data: FG.Transaction) {
state.transactions.push(data);
},
addTransactions(state, data: [FG.Transaction]) {
state.transactions.push(...data);
state.transactions.sort((a, b) => (a.time <= b.time ? -1 : 1));
},
reverseTransaction(state, data: { transaction: FG.Transaction; reversal: FG.Transaction }) {
const idx = state.transactions.findIndex(value => value.id === data.transaction.id);
data.transaction.reversal = data.reversal;
data.transaction.reversal_id = data.reversal.id;
if (idx > -1) state.transactions[idx] = data.transaction;
else state.transactions.push(data.transaction);
}
@ -70,7 +87,7 @@ const mutations: MutationTree<BalanceInterface> = {
const actions: ActionTree<BalanceInterface, StateInterface> = {
addShortcut({ commit, state, rootState }, shortcut) {
const sc = [...state.shortcuts, shortcut];
sc.sort().reverse();
sc.sort();
const user = <FG.User>rootState.user.currentUser;
return axios.put(`/users/${user.userid}/balance/shortcuts`, sc).then(() => {
@ -109,6 +126,37 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
})
.finally(() => commit('setLoading', false));
},
getBalances() {
return axios.get('/balance').then(({ data }: AxiosResponse<Array<BalancesResponse>>) => {
return data;
});
},
getTransactions(
{ commit, rootState },
payload: {
userid?: string;
filter?: {
limit?: number;
offset?: number;
from?: Date;
to?: Date;
showReversals?: boolean;
showCancelled?: boolean;
};
}
) {
commit('setLoading');
if (!payload.userid) payload.userid = (<FG.User>rootState.user.currentUser).userid;
if (!payload.filter) payload.filter = { limit: 10 };
return axios
.get(`/users/${payload.userid}/balance/transactions`, { params: payload.filter || {} })
.then(({ data }: AxiosResponse<TransactionsResponse>) => {
data.transactions.forEach(t => fixTransaction(t));
commit('addTransactions', data.transactions);
return data;
})
.finally(() => commit('setLoading', false));
},
getLimit({ rootState, commit }) {
commit('setLoading');
axios
@ -125,18 +173,26 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
revert({ dispatch, commit }, transaction: FG.Transaction) {
return axios
.delete(`/balance/${transaction.id}`)
.then((response: AxiosResponse<FG.Transaction>) => {
commit('reverseTransaction', { transaction: transaction, reversal: response.data });
.then(({ data }: AxiosResponse<FG.Transaction>) => {
fixTransaction(data);
commit('reverseTransaction', { transaction: transaction, reversal: data });
dispatch('getBalance').catch(err => console.warn(err));
});
},
changeBalance({ dispatch, commit }, data: { amount: number; user: string; sender?: string }) {
changeBalance(
{ dispatch, commit, rootState },
data: { amount: number; user: string; sender?: string }
) {
commit('setLoading');
return axios
.put(`/users/${data.user}/balance`, data)
.then((response: AxiosResponse<FG.Transaction>) => {
const transaction = response.data;
transaction.time = new Date(transaction.time);
fixTransaction(transaction);
if (
data.user == rootState.user.currentUser?.userid ||
data.sender === rootState.user.currentUser?.userid
)
commit('addTransaction', transaction);
commit(state.balances.has(data.user) ? 'changeBalance' : 'setBalance', {
userid: data.user,
@ -152,6 +208,7 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
.catch(err => {
console.debug(err);
// Maybe Balance changed
void dispatch('getTransactions', {});
return dispatch('getBalance', data.sender ? data.sender : data.user);
})
.finally(() => commit('setLoading', false));

View File

@ -3,7 +3,7 @@
<q-card>
<q-form @submit="save" @reset="reset">
<q-card-section class="fit row justify-start content-center items-center">
<q-card-section class="fit ">
<q-card-section class="fit">
<div class="text-h6">Veranstaltung erstellen</div>
</q-card-section>
<q-select
@ -45,12 +45,12 @@
<job
:job="job"
:jobCanDelete="jobDeleteDisabled"
@setStart="setStart"
@setRequired="setRequired"
@setEnd="setEnd"
@setComment="setComment"
@setJobType="setJobType"
@removeJob="removeJob(index)"
@set-start="setStart"
@set-required="setRequired"
@set-end="setEnd"
@set-comment="setComment"
@set-job-type="setJobType"
@remove-job="removeJob(index)"
/>
</q-card>
</q-card-section>
@ -149,7 +149,7 @@ export default defineComponent({
event.value.id = NaN;
event.value.start = new Date();
event.value.description = '';
delete event.value['type'];
event.value.type = {id: -1, name: ''};
event.value.jobs = [Object.assign({}, newJob.value)];
}
function notEmpty(val: string) {

View File

@ -89,33 +89,33 @@ export default defineComponent({
function setStart(value: Date) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
emit('setStart', { job: props.job, value });
emit('set-start', { job: props.job, value });
}
function setEnd(value: Date) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
emit('setEnd', { job: props.job, value });
emit('set-end', { job: props.job, value });
}
function setComment(value: string) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
emit('setComment', { job: props.job, value });
emit('set-comment', { job: props.job, value });
}
function setJobType(value: FG.JobType) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
console.log('setJobType', value);
emit('setJobType', { job: props.job, value });
emit('set-job-type', { job: props.job, value });
}
function setRequired(value: number) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
emit('setRequired', { job: props.job, value });
emit('set-required', { job: props.job, value });
}
function removeJob() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
emit('removeJob');
emit('remove-job');
}
function notEmpty(val: string) {

View File

@ -1,25 +1,5 @@
import { FG_Plugin } from 'src/plugins';
/*const permissions = {
// Show own and others balance
SHOW: 'balance_show',
SHOW_OTHER: 'balance_show_others',
// Credit balance (give)
CREDIT: 'balance_credit',
// Debit balance (take)
DEBIT: 'balance_debit',
// Debit own balance only
DEBIT_OWN: 'balance_debit_own',
// Send from to other
SEND: 'balance_send',
// Send from other to another
SEND_OTHER: 'balance_send_others',
// Can set limit for users
SET_LIMIT: 'balance_set_limit',
//Allow sending / sub while exceeding the set limit
EXCEED_LIMIT: 'balance_exceed_limit'
};*/
const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
{
title: 'Dienste',

View File

@ -31,12 +31,7 @@
label="Zeit"
filled
/>
<q-select
class="col-xs-12 col-sm-6 q-px-sm"
:options="options"
v-model="option"
filled
/>
<q-select class="col-xs-12 col-sm-6 q-px-sm" :options="options" v-model="option" filled />
</div>
</q-card-section>
<q-card-actions align="right" v-if="!isEdit">
@ -59,10 +54,10 @@ export default defineComponent({
name: 'Sessions',
props: {
session: {
required: true,
required: true
}
},
},
setup(props: {session: FG.Session}, { root }) {
setup(props: { session: FG.Session }, { root }) {
const store = <Store<StateInterface>>root.$store;
const options = ref(['Minuten', 'Stunden', 'Tage']);
const option = ref<string>(options.value[0]);
@ -92,7 +87,7 @@ export default defineComponent({
}
function deleteSession(token: string) {
store.dispatch('session/deleteSession', token).catch((error) => {
store.dispatch('session/deleteSession', token).catch(error => {
console.warn(error);
});
}
@ -113,7 +108,7 @@ export default defineComponent({
return (lifetime.value / (60 * 60 * 24)).toFixed(2);
}
},
set: (val) => {
set: val => {
if (val) {
switch (option.value) {
case options.value[0]:
@ -127,7 +122,7 @@ export default defineComponent({
break;
}
}
},
}
});
function edit(value: boolean) {
@ -139,11 +134,8 @@ export default defineComponent({
console.log(lifetime.value);
isEdit.value = false;
void store
.dispatch(
'session/updateSession',
{lifetime: lifetime.value, token: props.session.token}
)
.catch((error) => {
.dispatch('session/updateSession', { lifetime: lifetime.value, token: props.session.token })
.catch(error => {
console.log(error);
});
}
@ -159,8 +151,8 @@ export default defineComponent({
option,
lifetime,
computedLifetime,
save,
save
};
},
}
});
</script>

View File

@ -25,7 +25,7 @@ function loadCurrentSession() {
const state: SessionInterface = {
sessions: [],
currentSession: loadCurrentSession() || undefined,
loading: false,
loading: false
};
const mutations: MutationTree<SessionInterface> = {
@ -44,11 +44,11 @@ const mutations: MutationTree<SessionInterface> = {
state.loading = value;
},
updateSession(state, session: FG.Session) {
const index = state.sessions.findIndex((x) => x.token == session.token);
const index = state.sessions.findIndex(x => x.token == session.token);
if (index > -1) {
state.sessions[index] = session;
}
},
}
};
const actions: ActionTree<SessionInterface, StateInterface> = {
@ -65,7 +65,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
commit('setCurrentSession', response.data.session);
commit('user/setCurrentUser', response.data.user, { root: true });
commit('user/setCurrentPermissions', response.data.permissions, {
root: true,
root: true
});
})
.catch((error: AxiosError) => {
@ -78,7 +78,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
*/
logout({ dispatch, rootState }) {
if (rootState.session.currentSession) {
dispatch('deleteSession', rootState.session.currentSession.token).catch((error) => {
dispatch('deleteSession', rootState.session.currentSession.token).catch(error => {
console.log(error);
void dispatch('clearCurrent', false);
});
@ -97,7 +97,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
if (token === rootState.session.currentSession?.token) {
void dispatch('clearCurrent', false);
} else {
dispatch('getSessions').catch((error) => {
dispatch('getSessions').catch(error => {
throw error;
});
}
@ -116,7 +116,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
void Router.push({
name: 'login',
query: redirect ? { redirect: Router.currentRoute.fullPath } : {},
params: { logout: 'true' },
params: { logout: 'true' }
}).then(() => {
commit('clearCurrentSession');
commit('user/clearCurrentUser', null, { root: true });
@ -132,7 +132,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
axios
.get('/auth')
.then((response: AxiosResponse<FG.Session[]>) => {
response.data.forEach((session) => {
response.data.forEach(session => {
session.expires = new Date(session.expires);
});
commit('setSessions', response.data);
@ -143,7 +143,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
commit('setCurrentSession', currentSession);
}
})
.catch((error) => {
.catch(error => {
throw error;
})
.finally(() => {
@ -160,7 +160,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
commit('setCurrentSession', response.data);
}
})
.catch((err) => console.log(err))
.catch(err => console.log(err))
.finally(() => {
commit('setLoading', false);
});
@ -173,7 +173,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
return axios.post('/auth/reset', data).catch((error: AxiosError) => {
return Promise.reject(error.response);
});
},
}
};
const getters: GetterTree<SessionInterface, StateInterface> = {
@ -185,7 +185,7 @@ const getters: GetterTree<SessionInterface, StateInterface> = {
},
loading(state) {
return state.loading;
},
}
};
const sessions: Module<SessionInterface, StateInterface> = {
@ -193,7 +193,7 @@ const sessions: Module<SessionInterface, StateInterface> = {
state,
mutations,
actions,
getters,
getters
};
export default sessions;

View File

@ -9,3 +9,8 @@ export function hasPermissions(needed: string[], store: Store<StateInterface>) {
const permissions = store.state.user.currentPermissions;
return needed.every(value => permissions.includes(value));
}
export function hasSomePermissions(needed: string[], store: Store<StateInterface>) {
const permissions = store.state.user.currentPermissions;
return needed.some(value => permissions.includes(value));
}