release v2.0.0 #4

Merged
crimsen merged 481 commits from develop into master 2024-01-18 15:15:08 +00:00
3 changed files with 121 additions and 327 deletions
Showing only changes of commit 06256f651a - Show all commits

View File

@ -47,314 +47,6 @@
/> />
</div> </div>
</template> </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"
option-value="id"
filled
dense
autofocus
@keyup.enter="scope.set"
/>
</q-popup-edit>
</q-td>
<q-td key="tags" :props="drinks_props">
<q-badge
v-for="tag in drinks_props.row.tags"
:key="`${drinks_props.row.id}-${tag.id}`"
class="q-ma-xs"
rounded
:style="`background-color: ${tag.color}`"
>
{{ tag.name }}
</q-badge>
<q-popup-edit
v-model="drinks_props.row.tags"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@update:modelValue="updateDrink(drinks_props.row)"
>
<template #default="scope">
<q-select
v-model="scope.value"
multiple
:options="tags"
label="Tags"
option-label="name"
filled
>
<template #selected-item="item">
<q-chip
removable
:tabindex="item.tabindex"
:style="`background-color: ${item.opt.color}`"
@remove="item.removeAtIndex(item.index)"
>
{{ item.opt.name }}
</q-chip>
</template>
<template #option="item">
<q-item v-bind="item.itemProps" v-on="item.itemEvents">
<q-chip :style="`background-color: ${item.opt.color}`">
<q-avatar
v-if="item.selected"
icon="mdi-check"
color="positive"
text-color="white"
/>
{{ item.opt.name }}
</q-chip>
</q-item>
</template>
</q-select>
</template>
</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_per_package" :props="drinks_props">
{{
drinks_props.row.cost_per_package
? `${drinks_props.row.cost_per_package.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_per_package"
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_per_volume" :props="drinks_props">
{{
drinks_props.row.cost_per_volume
? `${drinks_props.row.cost_per_volume.toFixed(3)}`
: 'o.A.'
}}
<q-popup-edit
v-if="
!(
!!drinks_props.row.cost_per_package &&
!!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_per_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-td key="receipt" :props="drinks_props">
</q-td>
</q-tr>
</template>-->
<template #item="props"> <template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4"> <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
<q-card> <q-card>
@ -459,12 +151,10 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, onBeforeMount, ComputedRef, computed, ref } from 'vue'; import { defineComponent, onBeforeMount, ComputedRef, computed, ref } from 'vue';
import DrinkPriceVolumesTable from 'src/plugins/pricelist/components/CalculationTable/DrinkPriceVolumesTable.vue';
import { useMainStore } from 'src/stores'; import { useMainStore } from 'src/stores';
import { Drink, usePricelistStore, DrinkPriceVolume } from 'src/plugins/pricelist/store'; import { Drink, usePricelistStore, DrinkPriceVolume } from 'src/plugins/pricelist/store';
import MinPriceSetting from 'src/plugins/pricelist/components/MinPriceSetting.vue'; import MinPriceSetting from 'src/plugins/pricelist/components/MinPriceSetting.vue';
import NewDrink from 'src/plugins/pricelist/components/CalculationTable/NewDrink.vue'; import NewDrink from 'src/plugins/pricelist/components/CalculationTable/NewDrink.vue';
import BuildManual from 'src/plugins/pricelist/components/CalculationTable/BuildManual.vue';
import SearchInput from './SearchInput.vue'; import SearchInput from './SearchInput.vue';
import DrinkPriceVolumes from 'src/plugins/pricelist/components/CalculationTable/DrinkPriceVolumes.vue'; import DrinkPriceVolumes from 'src/plugins/pricelist/components/CalculationTable/DrinkPriceVolumes.vue';
import DrinkModify from './DrinkModify.vue'; import DrinkModify from './DrinkModify.vue';
@ -487,10 +177,6 @@ export default defineComponent({
const store = usePricelistStore(); const store = usePricelistStore();
onBeforeMount(() => { onBeforeMount(() => {
// void store.getDrinkTypes(true);
// void store.getTags();
//void store.getDrinks();
//void store.get_min_prices();
void store.getPriceCalcColumn(user); void store.getPriceCalcColumn(user);
}); });
@ -683,10 +369,9 @@ export default defineComponent({
drinkPic.value = undefined; drinkPic.value = undefined;
} }
function savePicture(drink: Drink) { async function savePicture(drinkPic: File) {
console.log('hier bin ich!!!', drinkPic.value); if (editDrink.value) {
if (drinkPic.value && drinkPic.value instanceof File) { await store.upload_drink_picture(editDrink.value, drinkPic).catch((response: Response) => {
store.upload_drink_picture(drink, drinkPic.value).catch((response: Response) => {
if (response && response.status == 400) { if (response && response.status == 400) {
onPictureRejected(); onPictureRejected();
} }
@ -694,8 +379,10 @@ export default defineComponent({
} }
} }
function deletePicture(drink: Drink) { async function deletePicture() {
void store.delete_drink_picture(drink); if (editDrink.value) {
await store.delete_drink_picture(editDrink.value);
}
} }
const search = ref<Search>({ const search = ref<Search>({
@ -705,7 +392,12 @@ export default defineComponent({
}); });
const editDrink = ref(); const editDrink = ref();
async function editing_drink(drink: Drink, toDeleteObjects: DeleteObjects) { async function editing_drink(
drink: Drink,
toDeleteObjects: DeleteObjects,
drinkPic: File | undefined,
deletePic: boolean
) {
notLoading.value = false; notLoading.value = false;
for (const ingredient of toDeleteObjects.ingredients) { for (const ingredient of toDeleteObjects.ingredients) {
await store.deleteIngredient(ingredient); await store.deleteIngredient(ingredient);
@ -718,6 +410,22 @@ export default defineComponent({
} }
console.log(drink); console.log(drink);
await store.updateDrink(drink); await store.updateDrink(drink);
if (deletePic || drinkPic) {
let loading = imageloading.value.find((a) => a.id === drink.id);
if (loading) {
loading.loading = true;
} else {
loading = { id: drink.id, loading: true };
imageloading.value.push(loading);
}
if (deletePic) {
await deletePicture();
}
if (drinkPic instanceof File) {
await savePicture(drinkPic);
}
loading.loading = false;
}
editDrink.value = undefined; editDrink.value = undefined;
notLoading.value = true; notLoading.value = true;
} }
@ -728,6 +436,15 @@ export default defineComponent({
const notLoading = ref(true); const notLoading = ref(true);
const imageloading = ref<Array<{ id: number; loading: boolean }>>([]);
function getImageLoading(id: number) {
const loading = imageloading.value.find((a) => a.id === id);
if (loading) {
return loading.loading;
}
return false;
}
return { return {
drinks: computed(() => store.drinks), drinks: computed(() => store.drinks),
pagination, pagination,
@ -751,6 +468,7 @@ export default defineComponent({
editing_drink, editing_drink,
get_volumes, get_volumes,
notLoading, notLoading,
getImageLoading,
}; };
}, },
}); });

View File

@ -3,6 +3,30 @@
<q-card-section> <q-card-section>
<div class="text-h6">Getränk Bearbeiten</div> <div class="text-h6">Getränk Bearbeiten</div>
</q-card-section> </q-card-section>
<q-card-section>
<q-img :src="image" style="max-height: 256px" fit="contain" />
<div class="full-width row">
<div class="col-10 q-pa-sm">
<q-file
v-model="drinkPic"
filled
clearable
dense
@update:model-value="imagePreview"
@clear="imgsrc = undefined"
>
<template #prepend>
<q-icon name="mdi-image" />
</template>
</q-file>
</div>
<div class="col-2 q-pa-sm text-right">
<q-btn round icon="mdi-delete" color="negative" size="sm" @click="delete_pic">
<q-tooltip> Bild entfernen </q-tooltip>
</q-btn>
</div>
</div>
</q-card-section>
<q-card-section> <q-card-section>
<q-select <q-select
v-model="edit_drink.tags" v-model="edit_drink.tags"
@ -101,6 +125,7 @@ import { Drink, DrinkPriceVolume, usePricelistStore } from '../store';
import DrinkPriceVolumes from './CalculationTable/DrinkPriceVolumes.vue'; import DrinkPriceVolumes from './CalculationTable/DrinkPriceVolumes.vue';
import { clone, calc_min_prices, DeleteObjects } from '../utils/utils'; import { clone, calc_min_prices, DeleteObjects } from '../utils/utils';
import BuildManual from 'src/plugins/pricelist/components/CalculationTable/BuildManual.vue'; import BuildManual from 'src/plugins/pricelist/components/CalculationTable/BuildManual.vue';
import config from 'src/config';
export default defineComponent({ export default defineComponent({
name: 'DrinkModify', name: 'DrinkModify',
components: { BuildManual, DrinkPriceVolumes }, components: { BuildManual, DrinkPriceVolumes },
@ -111,7 +136,12 @@ export default defineComponent({
}, },
}, },
emits: { emits: {
save: (drink: Drink, toDeleteObjects: DeleteObjects) => drink && toDeleteObjects, save: (
drink: Drink,
toDeleteObjects: DeleteObjects,
drinkPic: File | undefined,
deletePic: boolean
) => drink && toDeleteObjects || drinkPic || deletePic,
cancel: () => true, cancel: () => true,
}, },
setup(props, { emit }) { setup(props, { emit }) {
@ -130,8 +160,9 @@ export default defineComponent({
const edit_drink = ref<Drink>(); const edit_drink = ref<Drink>();
function save() { function save() {
emit('save', <Drink>edit_drink.value, toDeleteObjects.value); emit('save', <Drink>edit_drink.value, toDeleteObjects.value, drinkPic.value, deletePic.value);
} }
function cancel() { function cancel() {
emit('cancel'); emit('cancel');
} }
@ -147,17 +178,14 @@ export default defineComponent({
function deletePrice(price: FG.DrinkPrice) { function deletePrice(price: FG.DrinkPrice) {
toDeleteObjects.value.prices.push(price); toDeleteObjects.value.prices.push(price);
console.log('toDelete', toDeleteObjects.value);
} }
function deleteVolume(volume: DrinkPriceVolume) { function deleteVolume(volume: DrinkPriceVolume) {
toDeleteObjects.value.volumes.push(volume); toDeleteObjects.value.volumes.push(volume);
console.log('toDelete', toDeleteObjects.value);
} }
function deleteIngredient(ingredient: FG.Ingredient) { function deleteIngredient(ingredient: FG.Ingredient) {
toDeleteObjects.value.ingredients.push(ingredient); toDeleteObjects.value.ingredients.push(ingredient);
console.log('toDelete', toDeleteObjects.value);
} }
function addStep(event: string) { function addStep(event: string) {
@ -168,6 +196,45 @@ export default defineComponent({
edit_drink.value?.receipt?.splice(event, 1); edit_drink.value?.receipt?.splice(event, 1);
} }
const drinkPic = ref();
const imgsrc = ref();
const deletePic = ref(false);
function delete_pic() {
deletePic.value = true;
imgsrc.value = undefined;
drinkPic.value = undefined;
if (edit_drink.value) {
edit_drink.value.uuid = '';
}
}
function imagePreview() {
if (drinkPic.value && drinkPic.value instanceof File) {
let reader = new FileReader();
reader.onload = (e) => {
imgsrc.value = e.target?.result;
};
reader.readAsDataURL(drinkPic.value);
}
}
const image = computed(() => {
if (deletePic.value) {
return 'no-image.svg';
}
if (imgsrc.value) {
return <string>imgsrc.value;
}
if (edit_drink.value?.uuid) {
return `${config.baseURL}/pricelist/picture/${edit_drink.value.uuid}?size=256`;
}
return 'no-image.svg';
});
return { return {
edit_drink, edit_drink,
save, save,
@ -179,6 +246,11 @@ export default defineComponent({
addStep, addStep,
deleteStep, deleteStep,
tags: computed(() => store.tags), tags: computed(() => store.tags),
image,
imgsrc,
drinkPic,
imagePreview,
delete_pic,
}; };
}, },
}); });

View File

@ -204,6 +204,7 @@ export const usePricelistStore = defineStore({
this.drinks[index] = _drink; this.drinks[index] = _drink;
} }
calc_all_min_prices(this.drinks, this.min_prices); calc_all_min_prices(this.drinks, this.min_prices);
console.log('update drink', drink);
}, },
deleteDrink(drink: Drink) { deleteDrink(drink: Drink) {
api api
@ -250,7 +251,10 @@ export const usePricelistStore = defineStore({
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
}, },
}); });
drink.uuid = data.uuid; const _drink = this.drinks.find((a) => a.id === drink.id);
if (_drink) {
_drink.uuid = data.uuid;
}
}, },
async delete_drink_picture(drink: Drink) { async delete_drink_picture(drink: Drink) {
await api.delete(`pricelist/drinks/${drink.id}/picture`); await api.delete(`pricelist/drinks/${drink.id}/picture`);