<template> <q-table v-model:pagination="pagination" title="Kalkulationstabelle" :columns="columns" :rows="drinks" :visible-columns="visibleColumn" :dense="$q.screen.lt.md" row-key="id" virtual-scroll :rows-per-page-options="[0]" > <template #header="props"> <q-tr :props="props"> <q-th auto-width /> <q-th v-for="col in props.cols" :key="col.name" :props="props"> {{ col.label }} </q-th> </q-tr> </template> <template #top-right> <div class="row justify-end q-gutter-sm"> <q-btn label="Aufpreise"> <q-menu anchor="center middle" self="center middle"> <min-price-setting /> </q-menu> </q-btn> <q-btn label="neues Getränk" color="positive" icon-right="add"> <q-menu v-model="showNewDrink" anchor="center middle" self="center middle"> <new-drink @close="showNewDrink = false" /> </q-menu> </q-btn> <q-select v-model="visibleColumn" multiple filled dense options-dense display-value="Sichtbarkeit" emit-value map-options :options="[...columns, ...column_calc, ...column_prices]" option-value="name" options-cover /> </div> </template> <template #body="drinks_props"> <q-tr :props="drinks_props"> <q-td auto-width> <q-btn v-if="drinks_props.row.volumes.length === 0" size="xs" color="negative" round dense icon="mdi-delete" class="q-mx-sm" @click="deleteDrink(drinks_props.row)" /> </q-td> <q-td key="picture" :props="drinks_props" style="min-width: 256px"> <q-img loading="lazy" :src=" drinks_props.row.uuid ? `/api/pricelist/picture/${drinks_props.row.uuid}?size=256` : 'no-image.svg' " placeholder-src="no-image.svg" > <template #error> <div class="absolute-full flex flex-center bg-negative text-white"> Cannot load image </div> </template> </q-img> <q-popup-edit v-model="drinkPic" @update:modelValue="savePicture(drinks_props.row)"> <template #default="scope"> <div class="full-width row"> <q-file v-model="scope.value" filled> <template #prepend> <q-icon name="attach_file" /> </template> </q-file> <div class="full-width row justify-between"> <q-btn label="Abbrechen" flat color="primary" @click="scope.cancel" /> <q-btn label="Löschen" flat color="primary" @click=" scope.cancel(); deletePicture(drinks_props.row); " /> <q-btn label="Speichern" flat color="primary" @click="scope.set" /> </div> </div> </template> </q-popup-edit> </q-td> <q-td key="name" :props="drinks_props"> {{ drinks_props.row.name }} <q-popup-edit v-slot="scope" v-model="drinks_props.row.name" buttons label-cancel="Abbrechen" label-set="Speichern" @update:modelValue="updateDrink(drinks_props.row)" > <q-input v-model="scope.value" filled dense autofocus clearable @keyup.enter="scope.set" /> </q-popup-edit> </q-td> <q-td key="drink_type" :props="drinks_props"> {{ drinks_props.row.type.name }} <q-popup-edit v-slot="scope" v-model="drinks_props.row.type" buttons label-cancel="Abbrechen" label-set="Speichern" @update:modelValue="updateDrink(drinks_props.row)" > <q-select v-model="scope.value" :options="drinkTypes" option-label="name" filled dense autofocus @keyup.enter="scope.set" /> </q-popup-edit> </q-td> <q-td key="article_id" :props="drinks_props"> {{ drinks_props.row.article_id || 'o.A.' }} <q-popup-edit v-slot="scope" v-model="drinks_props.row.article_id" buttons label-cancel="Abbrechen" label-set="Speichern" @update:modelValue="updateDrink(drinks_props.row)" > <q-input v-model="scope.value" filled dense autofocus clearable @keyup.enter="scope.set" /> </q-popup-edit> </q-td> <q-td key="volume_package" :props="drinks_props"> {{ drinks_props.row.volume ? `${drinks_props.row.volume} L` : 'o.A.' }} <q-popup-edit v-if=" !drinks_props.row.volumes.some((volume) => volume.ingredients.some((ingredient) => ingredient.drink_ingredient) ) " v-slot="scope" v-model.number="drinks_props.row.volume" buttons label-cancel="Abbrechen" label-set="Speichern" @update:modelValue="updateDrink(drinks_props.row)" > <q-input v-model.number="scope.value" filled dense autofocus type="number" clearable step="0.01" min="0" suffix="L" @keyup.enter="scope.set" /> </q-popup-edit> </q-td> <q-td key="package_size" :props="drinks_props"> {{ drinks_props.row.package_size || 'o.A.' }} <q-popup-edit v-if=" !drinks_props.row.volumes.some((volume) => volume.ingredients.some((ingredient) => ingredient.drink_ingredient) ) " v-slot="scope" v-model="drinks_props.row.package_size" buttons label-cancel="Abbrechen" label-set="Speichern" @update:modelValue="updateDrink(drinks_props.row)" > <q-input v-model.number="scope.value" filled dense autofocus type="number" min="0" @keyup.enter="scope.set" /> </q-popup-edit> </q-td> <q-td key="cost_price_package_netto" :props="drinks_props"> {{ drinks_props.row.cost_price_package_netto ? `${drinks_props.row.cost_price_package_netto.toFixed(2)}€` : 'o.A.' }} <q-popup-edit v-if=" !drinks_props.row.volumes.some((volume) => volume.ingredients.some((ingredient) => ingredient.drink_ingredient) ) " v-slot="scope" v-model="drinks_props.row.cost_price_package_netto" buttons label-cancel="Abbrechen" label-set="Speichern" @update:modelValue="updateDrink(drinks_props.row)" > <q-input v-model.number="scope.value" filled dense autofocus type="number" step="0.01" min="0" suffix="€" @keyup.enter="scope.set" /> </q-popup-edit> </q-td> <q-td key="cost_price_pro_volume" :props="drinks_props"> {{ drinks_props.row.cost_price_pro_volume ? `${drinks_props.row.cost_price_pro_volume.toFixed(3)}€` : 'o.A.' }} <q-popup-edit v-if=" !( !!drinks_props.row.cost_price_package_netto && !!drinks_props.row.volume && !!drinks_props.row.package_size ) && !drinks_props.row.volumes.some((volume) => volume.ingredients.some((ingredient) => ingredient.drink_ingredient) ) " v-slot="scope" v-model="drinks_props.row.cost_price_pro_volume" buttons label-cancel="Abbrechen" label-set="Speichern" @update:modelValue="updateDrink(drinks_props.row)" > <q-input v-model.number="scope.value" filled dense autofocus type="number" min="0" step="0.1" suffix="€" @keyup.enter="scope.set" /> </q-popup-edit> </q-td> <q-td key="volumes" :props="drinks_props"> <drink-price-volumes-table :rows="drinks_props.row.volumes" :visible-columns="visibleColumn" :columns="column_calc" :drink="drinks_props.row" @updateDrink="updateDrink(drinks_props.row)" /> </q-td> </q-tr> </template> </q-table> </template> <script lang="ts"> import { defineComponent, onBeforeMount, ComputedRef, computed, ref } from 'vue'; import DrinkPriceVolumesTable from 'src/plugins/pricelist/components/CalculationTable/DrinkPriceVolumesTable.vue'; import { useMainStore } from 'src/store'; import { Drink, usePricelistStore } from 'src/plugins/pricelist/store'; import MinPriceSetting from 'src/plugins/pricelist/components/MinPriceSetting.vue'; import NewDrink from 'src/plugins/pricelist/components/CalculationTable/NewDrink.vue'; import { Notify } from 'quasar'; function sort(a: string | number, b: string | number) { if (a > b) return 1; if (b > a) return -1; return 0; } export default defineComponent({ name: 'CalculationTable', components: { MinPriceSetting, DrinkPriceVolumesTable, NewDrink }, setup() { const mainStore = useMainStore(); const store = usePricelistStore(); onBeforeMount(() => { store.getPriceCalcColumn(user); }); const user = mainStore.currentUser.userid; const columns = [ { name: 'picture', label: 'Bild', }, { name: 'name', label: 'Getränkename', field: 'name', sortable: true, sort, }, { name: 'article_id', label: 'Artikelnummer', field: 'article_id', sortable: true, sort, }, { name: 'drink_type', label: 'Kategorie', field: 'type', format: (val: FG.DrinkType) => `${val.name}`, sortable: true, sort: (a: FG.DrinkType, b: FG.DrinkType) => sort(a.name, b.name), }, { name: 'volume_package', label: 'Inhalt in l des Gebinde', field: 'volume', sortable: true, sort, }, { name: 'package_size', label: 'Gebindegröße', field: 'package_size', sortable: true, sort, }, { name: 'cost_price_package_netto', label: 'Preis Netto/Gebinde', field: 'cost_price_package_netto', format: (val: number | null) => (val ? `${val.toFixed(3)}€` : ''), sortable: true, sort, }, { name: 'cost_price_pro_volume', label: 'Preis mit 19%/Liter', field: 'cost_price_pro_volume', format: (val: number | null) => (val ? `${val.toFixed(3)}€` : ''), sortable: true, sort: (a: ComputedRef, b: ComputedRef) => sort(a.value, b.value), }, { name: 'volumes', label: 'Preiskalkulation', field: 'volumes', }, ]; const column_calc = [ { name: 'volume', label: 'Abgabe in l', field: 'volume', }, { name: 'min_prices', label: 'Minimal Preise', field: 'min_prices', }, { name: 'prices', label: 'Preise', field: 'prices', }, ]; const column_prices = [ { name: 'price', label: 'Preis', field: 'price', format: (val: number) => `${val.toFixed(2)}€`, }, { name: 'description', label: 'Beschreibung', field: 'description', }, { name: 'public', label: 'Öffentlich', field: 'public', }, ]; const visibleColumn = computed({ get: () => store.pricecalc_columns, set: (val) => { store.updatePriceCalcColumn(user, val); }, }); // eslint-disable-next-line vue/return-in-computed-property const pagination = computed(() => { rowsPerPage: store.drinks.length; }); const drinkTypes = computed(() => store.drinkTypes); function updateDrink(drink: Drink) { void store.updateDrink(drink); } function deleteDrink(drink: Drink) { store.deleteDrink(drink); } const showNewDrink = ref(false); const drinkPic = ref<File>(); function onPictureRejected() { Notify.create({ group: false, type: 'negative', message: 'Datei zu groß oder keine gültige Bilddatei.', timeout: 10000, progress: true, actions: [{ icon: 'mdi-close', color: 'white' }], }); drinkPic.value = undefined; } function savePicture(drink: Drink) { console.log('hier bin ich!!!', drinkPic.value); if (drinkPic.value && drinkPic.value instanceof File) { store.upload_drink_picture(drink, drinkPic.value).catch((response: Response) => { if (response && response.status == 400) { onPictureRejected(); } }); } } function deletePicture(drink: Drink) { void store.delete_drink_picture(drink); } return { drinks: computed(() => store.drinks), pagination, columns, column_calc, column_prices, visibleColumn, drinkTypes, updateDrink, deleteDrink, showNewDrink, drinkPic, savePicture, deletePicture, console, }; }, }); </script> <style scoped></style>