release v2.0.0 #4

Merged
crimsen merged 481 commits from develop into master 2024-01-18 15:15:08 +00:00
8 changed files with 342 additions and 32 deletions
Showing only changes of commit 7289a1724d - Show all commits

View File

@ -132,5 +132,6 @@ declare namespace FG {
interface Tag { interface Tag {
id: number; id: number;
name: string; name: string;
color: string;
} }
} }

View File

@ -123,26 +123,6 @@
/> />
</q-popup-edit> </q-popup-edit>
</q-td> </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="drink_type" :props="drinks_props"> <q-td key="drink_type" :props="drinks_props">
{{ drinks_props.row.type.name }} {{ drinks_props.row.type.name }}
<q-popup-edit <q-popup-edit
@ -165,6 +145,79 @@
/> />
</q-popup-edit> </q-popup-edit>
</q-td> </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"> <q-td key="volume_package" :props="drinks_props">
{{ drinks_props.row.volume ? `${drinks_props.row.volume} L` : 'o.A.' }} {{ drinks_props.row.volume ? `${drinks_props.row.volume} L` : 'o.A.' }}
<q-popup-edit <q-popup-edit
@ -347,7 +400,8 @@ export default defineComponent({
const store = usePricelistStore(); const store = usePricelistStore();
onBeforeMount(() => { onBeforeMount(() => {
void store.getDrinkTypes(); void store.getDrinkTypes(true);
void store.getTags();
void store.getExtraIngredients(); void store.getExtraIngredients();
void store.get_min_prices(); void store.get_min_prices();
store.getPriceCalcColumn(user); store.getPriceCalcColumn(user);
@ -367,13 +421,7 @@ export default defineComponent({
sortable: true, sortable: true,
sort, sort,
}, },
{
name: 'article_id',
label: 'Artikelnummer',
field: 'article_id',
sortable: true,
sort,
},
{ {
name: 'drink_type', name: 'drink_type',
label: 'Kategorie', label: 'Kategorie',
@ -382,6 +430,28 @@ export default defineComponent({
sortable: true, sortable: true,
sort: (a: FG.DrinkType, b: FG.DrinkType) => sort(a.name, b.name), sort: (a: FG.DrinkType, b: FG.DrinkType) => sort(a.name, b.name),
}, },
{
name: 'tags',
label: 'Tags',
field: 'tags',
format: (val: Array<FG.Tag>) => {
let retVal = '';
val.forEach((tag, index) => {
if (index > 0) {
retVal += ', ';
}
retVal += tag.name;
});
return retVal;
},
},
{
name: 'article_id',
label: 'Artikelnummer',
field: 'article_id',
sortable: true,
sort,
},
{ {
name: 'volume_package', name: 'volume_package',
label: 'Inhalt in l des Gebinde', label: 'Inhalt in l des Gebinde',
@ -547,6 +617,7 @@ export default defineComponent({
console, console,
search, search,
filter, filter,
tags: computed(() => store.tags),
}; };
}, },
}); });

View File

@ -60,7 +60,6 @@ export default defineComponent({
const actualDrinkType = ref(emptyDrinkType); const actualDrinkType = ref(emptyDrinkType);
onBeforeMount(() => { onBeforeMount(() => {
console.log(store);
void store.getDrinkTypes(); void store.getDrinkTypes();
}); });
const rows = computed(() => store.drinkTypes); const rows = computed(() => store.drinkTypes);

View File

@ -31,6 +31,19 @@
/> />
</div> </div>
</template> </template>
<template #body-cell-tags="props">
<q-td :props="props">
<q-badge
v-for="tag in props.row.tags"
:key="`${props.row.id}-${tag.id}`"
class="q-ma-xs"
rounded
:style="`background-color: ${tag.color}`"
>
{{ tag.name }}
</q-badge>
</q-td>
</template>
<template #body-cell-volumes="props"> <template #body-cell-volumes="props">
<q-td :props="props"> <q-td :props="props">
<q-table <q-table
@ -150,6 +163,22 @@ export default defineComponent({
sortable: true, sortable: true,
sort: (a: FG.DrinkType, b: FG.DrinkType) => sort(a.name, b.name), sort: (a: FG.DrinkType, b: FG.DrinkType) => sort(a.name, b.name),
}, },
{
name: 'tags',
label: 'Tags',
field: 'tags',
format: (val: Array<FG.Tag>) => {
let retVal = '';
val.forEach((tag, index) => {
if (index > 0) {
retVal += ', ';
}
retVal += tag.name;
});
return retVal;
},
},
{ {
name: 'volumes', name: 'volumes',
label: 'Preise', label: 'Preise',

View File

@ -0,0 +1,166 @@
<template>
<div>
<q-page padding>
<q-table title="Tags" :rows="rows" :row-key="(row) => row.id" :columns="columns">
<template #top-right>
<q-btn color="primary" icon="mdi-plus" label="Hinzufügen">
<q-menu v-model="popup" anchor="center middle" self="center middle" persistent>
<div>
<q-input
v-model="newTag.name"
filled
dense
label="Name"
class="q-pa-sm"
:rule="[notExists]"
/>
<q-color
:model-value="newTag.color"
class="q-pa-sm"
@change="
(val) => {
newTag.color = val;
}
"
/>
<div class="row q-gutter-sm justify-around">
<q-btn v-close-popup label="Abbrechen" />
<q-btn label="Speichern" color="primary" @click="save" />
</div>
</div>
</q-menu>
</q-btn>
</template>
<template #header="props">
<q-tr :props="props">
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
<q-th auto-width />
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
<q-td key="name" :props="props">
{{ props.row.name }}
<q-popup-edit
v-model="props.row.name"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@update:modelValue="updateTag(props.row)"
>
<template #default="scope">
<div class="fit row justify-around">
<q-input v-model="scope.value" :rules="[notExists]" dense filled />
</div>
</template>
</q-popup-edit>
</q-td>
<q-td key="color" :props="props">
<div class="full-width row q-gutter-sm justify-end items-center">
<div>
{{ props.row.color }}
</div>
<div class="color-box" :style="`background-color: ${props.row.color};`">&nbsp;</div>
</div>
<q-popup-edit
v-model="props.row.color"
buttons
label-cancel="Abbrechen"
label-set="Speichern"
@update:modelValue="updateTag(props.row)"
>
<template #default="slot">
<div class="fit row justify-around">
<q-color :model-value="slot.value" @change="(val) => (slot.value = val)" />
</div>
</template>
</q-popup-edit>
</q-td>
<q-td>
<q-btn
icon="mdi-delete"
color="negative"
round
size="sm"
@click="deleteTag(props.row)"
/>
</q-td>
</q-tr>
</template>
</q-table>
</q-page>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onBeforeMount, computed } from 'vue';
import { usePricelistStore } from '../store';
export default defineComponent({
name: 'Tags',
setup() {
const store = usePricelistStore();
onBeforeMount(() => {
void store.getTags();
});
const columns = [
{
name: 'name',
label: 'Name',
field: 'name',
},
{
name: 'color',
label: 'Farbe',
field: 'color',
},
];
const rows = computed(() => store.tags);
const emptyTag = {
id: -1,
color: '#1976d2',
name: '',
};
async function save() {
await store.setTag(newTag.value);
popup.value = false;
newTag.value = emptyTag;
}
const newTag = ref(emptyTag);
const popup = ref(false);
function notExists(val: string) {
const index = store.tags.findIndex((a) => a.name === val);
if (index > -1) {
return 'Tag existiert bereits.';
}
return true;
}
return {
columns,
rows,
newTag,
popup,
save,
updateTag: store.updateTag,
notExists,
deleteTag: store.deleteTag,
};
},
});
</script>
<style scoped>
.color-box {
min-width: 28px;
min-heigh: 28px;
max-width: 28px;
max-height: 28px;
border-width: 1px;
border-color: black;
border-radius: 5px;
}
</style>

View File

@ -23,10 +23,26 @@
" "
placeholder-src="no-image.svg" placeholder-src="no-image.svg"
> >
<div class="absolute-bottom-right text-subtitle2"> <div class="absolute-bottom-right justify-end">
<div class="text-subtitle1 text-right">
{{ props.row.name }} {{ props.row.name }}
</div> </div>
<div class="text-caption text-right">
{{ props.row.type.name }}
</div>
</div>
</q-img> </q-img>
<q-card-section>
<q-badge
v-for="tag in props.row.tags"
:key="`${props.row.id}-${tag.id}`"
class="text-caption"
rounded
:style="`background-color: ${tag.color}`"
>
{{ tag.name }}
</q-badge>
</q-card-section>
<build-manual-volume :volumes="props.row.volumes" /> <build-manual-volume :volumes="props.row.volumes" />
<q-card-section> <q-card-section>
<div class="text-h6">Anleitung</div> <div class="text-h6">Anleitung</div>

View File

@ -40,6 +40,9 @@
<q-tab-panel name="drink_types"> <q-tab-panel name="drink_types">
<drink-types /> <drink-types />
</q-tab-panel> </q-tab-panel>
<q-tab-panel name="tags">
<tags />
</q-tab-panel>
</q-tab-panels> </q-tab-panels>
</q-page> </q-page>
</div> </div>
@ -51,11 +54,12 @@ import { Screen } from 'quasar';
import DrinkTypes from 'src/plugins/pricelist/components/DrinkTypes.vue'; import DrinkTypes from 'src/plugins/pricelist/components/DrinkTypes.vue';
import CalculationTable from 'src/plugins/pricelist/components/CalculationTable.vue'; import CalculationTable from 'src/plugins/pricelist/components/CalculationTable.vue';
import ExtraIngredients from 'src/plugins/pricelist/components/ExtraIngredients.vue'; import ExtraIngredients from 'src/plugins/pricelist/components/ExtraIngredients.vue';
import Tags from '../components/Tags.vue';
import { usePricelistStore } from 'src/plugins/pricelist/store'; import { usePricelistStore } from 'src/plugins/pricelist/store';
export default defineComponent({ export default defineComponent({
name: 'Settings', name: 'Settings',
components: { ExtraIngredients, DrinkTypes, CalculationTable }, components: { ExtraIngredients, DrinkTypes, CalculationTable, Tags },
setup() { setup() {
interface Tab { interface Tab {
name: string; name: string;
@ -89,6 +93,7 @@ export default defineComponent({
{ name: 'pricelist', label: 'Getränke' }, { name: 'pricelist', label: 'Getränke' },
{ name: 'extra_ingredients', label: 'Zutaten' }, { name: 'extra_ingredients', label: 'Zutaten' },
{ name: 'drink_types', label: 'Getränketypen' }, { name: 'drink_types', label: 'Getränketypen' },
{ name: 'tags', label: 'Tags' },
]; ];
const tab = ref<string>('pricelist'); const tab = ref<string>('pricelist');

View File

@ -96,6 +96,7 @@ export const usePricelistStore = defineStore({
extraIngredients: [] as Array<FG.ExtraIngredient>, extraIngredients: [] as Array<FG.ExtraIngredient>,
pricecalc_columns: [] as Array<string>, pricecalc_columns: [] as Array<string>,
min_prices: [] as Array<number>, min_prices: [] as Array<number>,
tags: [] as Array<FG.Tag>,
}), }),
actions: { actions: {
@ -319,6 +320,28 @@ export const usePricelistStore = defineStore({
await api.delete(`pricelist/drinks/${drink.id}/picture`); await api.delete(`pricelist/drinks/${drink.id}/picture`);
drink.uuid = ''; drink.uuid = '';
}, },
async getTags() {
const { data } = await api.get<Array<FG.Tag>>('/pricelist/tags');
this.tags = data;
},
async setTag(tag: FG.Tag) {
const { data } = await api.post<FG.Tag>('/pricelist/tags', tag);
this.tags.push(data);
},
async updateTag(tag: FG.Tag) {
const { data } = await api.put<FG.Tag>(`/pricelist/tags/${tag.id}`, tag);
const index = this.tags.findIndex((a) => a.id === data.id);
if (index > -1) {
this.tags[index] = data;
}
},
async deleteTag(tag: FG.Tag) {
await api.delete(`/pricelist/tags/${tag.id}`);
const index = this.tags.findIndex((a) => a.id === tag.id);
if (index > -1) {
this.tags.splice(index, 1);
}
},
}, },
}); });