[TASK] Inital State

This commit is contained in:
chrosey
2020-01-13 16:09:25 +01:00
parent 0cef46b3b2
commit 42c50342ab
66 changed files with 53145 additions and 12594 deletions
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class="row pin-top">
<div class="col s12 m4 card darken-4 grey grey-text text-lighten-2">
<table class="card-content">
<tbody style="max-height:300px">
<tr
v-for="item in items"
v-show="item.count > 0"
:key="'bon_'+item.short"
>
<td class="right-align">
{{ item.count }} &times;
</td>
<td>{{ item.name }}</td>
<td class="right-align">
<TweenedNumber
:wert="item.sum"
:einheit="'€'"
:precision="2"
/>
</td>
</tr>
</tbody>
<tfoot>
<tr class="white-text">
<th class="right-align">
<TweenedNumber
:wert="totalCount"
/>
</th>
<th>
Artikel
</th>
<th class="right-align">
<TweenedNumber
:wert="totalSum"
:einheit="'€'"
:precision="2"
/>
</th>
</tr>
</tfoot>
</table>
</div>
<div class="col s12 m8">
<div class="row">
<div
v-for="a in items"
:key="'bon_btn_'+a.short"
class="col s3"
>
<button
class="waves-effect waves-light btn-large btn-flat col s12"
@click="a.count++;a.sum+=a.price;"
>
{{ a.short }}
</button>
</div>
</div>
<div class="row">
<div class="col s3">
<button
class="waves-effect waves-light btn-large orange col s12"
@click="resetBon"
>
Reset
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import TweenedNumber from './TweenedNumber';
export default {
name: "Calculator",
components: { TweenedNumber },
props: {
articles: {
type: Array,
default() { return []; }
}
},
data() {
return {
items: []
}
},
computed: {
totalSum() {
return this.items.reduce((total, item) => {
return total + item.sum;
}, 0);
},
totalCount() {
return this.items.reduce((total, item) => {
return total + item.count;
}, 0);
}
},
mounted() {
this.resetBon();
},
methods: {
resetBon() {
this.items = this.articles.map(a => {
return {
count: 0,
name: a.name,
short: a.short,
price: a.portion.price,
sum: 0,
}
});
}
}
}
</script>
<style>
</style>
+7
View File
@@ -0,0 +1,7 @@
.inventory-item {
transition: all .2s;
}
.inventory-item>* {
transition: all .2s .2s;
}
+153
View File
@@ -0,0 +1,153 @@
<template>
<tr
style="border: 0;"
class="inventory-item"
:class="{
'teal lighten-5' : item.Sale > 0,
'orange darken-2 white-text' : item.Sold %1 !=0,
'red darken-2 white-text' : item.Sale < 0,
}"
>
<th
class="right-align"
>
{{ item.article.name }}
</th>
<td
v-for="prop in ['start','fetched','end','lost']"
:key="item.article.short+'_'+prop"
class="border-bottom center-align"
:class="{active:classObject.active == prop}"
contenteditable
@keypress="restrictInput(prop, $event)"
@focus="onFocus(prop, $event)"
@blur="onBlur(prop, $event)"
>
{{ item[prop] }}
</td>
<td
class="right-align border-bottom border-diagram"
>
<TweenedNumber
:style="{
'border-image': 'linear-gradient(to right , rgba(0,150,136,.01) 0%, rgba(0,150,136,.3) '
+ item.Sold*100/total
+ '%, transparent '
+ item.Sold*100/total
+'%,transparent 100%) 1'
}"
:wert="item.Sold"
:einheit="item.article.PortionType"
:precision="item.PortionPrecision"
/>
</td>
<td
class="right-align border-bottom"
>
<TweenedNumber
:wert="item.Sale"
:einheit="'€'"
:precision="2"
/>
</td>
</tr>
</template>
<script>
import TweenedNumber from './TweenedNumber';
import './InventoryItem.css';
export default {
name:"InventoryItem",
components: {
TweenedNumber,
},
props: {
item: {
type: Object,
default() {
return {}
}
},
total: {
type: Number,
default: 0
}
},
data() {
return {
tweenedSold: 0,
tweenedSale: 0,
classObject: {active : null}
}
},
computed: {
SoldAnimated() {
return this.tweenedSold.toFixed(this.item.PortionPrecision);
},
SaleAnimated() {
return this.tweenedSale.toFixed(2);
}
},
watch: {
},
methods: {
setCaretPosition(el){
if (el != null) {
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el.firstChild, el.innerText.length);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
el.focus();
}
},
restrictInput(prop, event){
var x = event.key;
console.log(prop + " inserting " + x)
if (x === "Enter" ) {
event.target.blur();
}
if (isNaN(x) && x != ',') {
event.preventDefault();
}
},
onFocus(property, event){
this.classObject.active = property;
var range = document.createRange();
var sel = window.getSelection();
range.selectNodeContents(event.target);
sel.removeAllRanges();
sel.addRange(range);
},
onBlur(property, event) {
this.classObject.active = null;
var value = event.target.innerText;
value = value.replace(',', '.')
console.log(property + " left value: " + value);
if (!isNaN(value)) {
this.item[property] = Number(value);
}
},
onInput(property, event){
if (event.inputType == "insertText") {
var regex = /\d+\.?\d{0,2}/;
if (event.data.match(regex)){
var value = event.target.innerText;
this.item[property] = Number(value);
}
}
this.setCaretPosition(event.target);
},
}
}
</script>
<style>
</style>
+25
View File
@@ -0,0 +1,25 @@
table.condensed td,
table.condensed th {
padding: 5px 5px;
}
.border-bottom {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
}
.border-right {
border-right: 1px solid rgba(0, 0, 0, 0.12);
}
.border-diagram {
padding: 0;
}
.border-diagram>div {
margin-bottom: -3px;
border-radius: 0;
border-bottom: 3px solid transparent;
}
.active {
border-color: rgb(38, 166, 154);
border-width: 2px;
}
+151
View File
@@ -0,0 +1,151 @@
<template>
<table class="table condensed highlight">
<thead>
<tr>
<th class="right-align">
Artikel
</th>
<th class="center-align">
Beginn
</th>
<th class="center-align">
Zugang
</th>
<th class="center-align">
Ende
</th>
<th class="center-align">
Verlust
</th>
<th class="right-align">
Verkauft
</th>
<th class="right-align">
Summe
</th>
</tr>
</thead>
<tbody>
<InventoryItem
v-for="(item,index) in inventory"
:key="'item-'+index"
:item="item"
:total="sold"
/>
</tbody>
<tfoot>
<tr>
<th
colspan="6"
class="right-align"
>
Gesamtsumme:
</th>
<td class="right-align">
<TweenedNumber
:wert="sales"
:precision="2"
:einheit="'€'"
/>
</td>
</tr>
</tfoot>
</table>
</template>
<script>
import InventoryItem from "./InventoryItem";
import TweenedNumber from "./TweenedNumber";
import { InventoryArticle } from '../model/inventory_article';
import './InventoryTable.css';
export default {
name: "InventoryTable",
components: { InventoryItem, TweenedNumber },
props: {
articles: {
type: Array,
default() {
return [];
}
}
},
data() {
return {
inventory: []
};
},
computed: {
meta() {
return {
datum: new Date().toString(),
}
},
sales() {
var total_sales = this.inventory.reduce(function(total, item) {
return total + item.Sale;
}, 0);
return total_sales;
},
sold() {
var total_sold = this.inventory.reduce((total, item) => {
return total + item.Sold;
},0);
return total_sold;
}
},
mounted() {
this.$on('reset-inventur', this.resetInventory);
this.$on('export-inventur', this.exportInventory);
this.resetInventory();
},
methods: {
resetInventory() {
this.inventory = this.articles.map(item => {
return new InventoryArticle(item);
});
},
getInventurBlob() {
let inventur = this.inventory.map(T => {
return {
name: T.article.name,
preis: T.article.portion.price,
beginn: T.start,
zugang: T.fetched,
ende: T.end,
verlust: T.lost,
verkauft: T.Sold,
umsatz: T.Sale
};
});
const data = JSON.stringify({
meta: this.meta,
data: inventur
});
const blob = new Blob([data],{type: 'text/json' });
return blob;
},
exportInventory() {
var formData = new FormData();
formData.append('file', this.getInventurBlob(), 'inventur.json');
this.$http
.post(
"/api/inventur",
formData,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
)
.then(response => {
this.$M.toast({ html: response.body });
});
}
}
};
</script>
<style>
</style>
+63
View File
@@ -0,0 +1,63 @@
<template>
<nav
class="nav-extended"
:class="primaryColor"
>
<div class="nav-wrapper container" />
<div class="nav-content container">
<ul class="tabs tabs-transparent">
<li class="tab">
<a
class="active"
@click.stop.prevent="changeTab('inventory', $event)"
>Inventur</a>
</li>
<li class="tab">
<a
@click.stop.prevent="changeTab('article', $event)"
>Artikelliste</a>
</li>
<li class="tab">
<a
@click.stop.prevent="changeTab('calc', $event)"
>Rechner</a>
</li>
</ul>
</div>
</nav>
</template>
<script>
import M from 'materialize-css';
export default {
props: {
primaryColor: {
type: String,
default: "red"
}
},
data() {
return {
tabs: []
}
},
mounted() {
var tabs = document.getElementsByClassName("tabs")[0];
M.Tabs.init(tabs, {});
this.tabs = M.Tabs.getInstance(tabs);
},
methods: {
changeTab(tabId, event) {
this.view = tabId;
this.tabs._handleTabClick(event);
this.tabs.updateTabIndicator();
this.$emit("changed-tab", this.view);
},
}
}
</script>
<style>
</style>
+15
View File
@@ -0,0 +1,15 @@
.tweened {
display: flex;
align-content: end;
justify-content: end;
}
.tweened>div:first-child {
text-align: right;
min-width: 1.5rem;
}
.tweened>div:nth-child(0n+2) {
min-width: 1.5rem;
}
.tweened>div:nth-child(0n+3) {
min-width: 2rem;
}
+63
View File
@@ -0,0 +1,63 @@
<template>
<div class="tweened">
<div>{{ intAnimated }}</div>
<div v-if="precision>0">
,{{ deciAnimated }}
</div>
<div v-else />
<div>{{ einheit }}</div>
</div>
</template>
<script>
import gsap from 'gsap';
import './TweenedNumber.css';
export default {
name: "TweenedNumber",
props: {
wert: {
type: Number,
default: 0
},
einheit: {
type: String,
default: ""
},
precision: {
type: Number,
default: 0
}
},
data(){
return {
tweenedValue: 0,
}
},
computed: {
valueAnimated() {
return this.tweenedValue.toLocaleString(
"de-DE",
{
maximumFractionDigits: 2,
minimumFractionDigits: this.precision
}
);
},
intAnimated() {
return this.valueAnimated.split(',')[0];
},
deciAnimated() {
return this.valueAnimated.split(',')[1];
}
},
watch: {
wert: {
handler(newValue) {
gsap.to(this.$data, 0.5, {tweenedValue: newValue });
},
deep: true
}
}
}
</script>