[TASK] Inital State
This commit is contained in:
@@ -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 }} ×
|
||||
</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>
|
||||
@@ -0,0 +1,7 @@
|
||||
.inventory-item {
|
||||
transition: all .2s;
|
||||
}
|
||||
|
||||
.inventory-item>* {
|
||||
transition: all .2s .2s;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user