mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-02 04:39:54 -05:00
fixed some things but page param still broken
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<v-dialog max-width="70vw" min-height="80vh" activator="parent">
|
||||
<v-dialog max-width="70vw" min-height="80vh" :activator="activator">
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
Nachrichten
|
||||
{{ $t('Messages')}}
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
@@ -27,20 +27,20 @@
|
||||
divided
|
||||
multiple>
|
||||
<v-btn :value="MessageType.SUCCESS">
|
||||
<v-icon icon="fa-regular fa-eye" color="success"></v-icon>
|
||||
Success
|
||||
<v-icon icon="fa-regular fa-eye" color="success" class="me-2"></v-icon>
|
||||
{{$t('Success')}}
|
||||
</v-btn>
|
||||
<v-btn :value="MessageType.INFO">
|
||||
<v-icon icon="fa-regular fa-eye" color="info"></v-icon>
|
||||
Info
|
||||
<v-icon icon="fa-regular fa-eye" color="info" class="me-2"></v-icon>
|
||||
{{$t('Information')}}
|
||||
</v-btn>
|
||||
<v-btn :value="MessageType.WARNING">
|
||||
<v-icon icon="fa-regular fa-eye" color="warning"></v-icon>
|
||||
Warning
|
||||
<v-icon icon="fa-regular fa-eye" color="warning" class="me-2"></v-icon>
|
||||
{{$t('Warning')}}
|
||||
</v-btn>
|
||||
<v-btn :value="MessageType.ERROR">
|
||||
<v-icon icon="fa-regular fa-eye" color="error"></v-icon>
|
||||
Error
|
||||
<v-icon icon="fa-regular fa-eye" color="error" class="me-2"></v-icon>
|
||||
{{$t('Error')}}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
|
||||
@@ -61,9 +61,15 @@
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.msg="{ value }">
|
||||
<b v-if="value.title">{{ value.title }}<br/></b>
|
||||
{{ value.text }}
|
||||
</template>
|
||||
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-icon icon="$search" @click="showDetailDialog = true; detailItem = item"></v-icon>
|
||||
<v-icon class="ms-1" icon="mdi-content-copy" @click="copy(JSON.stringify({'type': item.type, 'createdAt': item.createdAt, 'msg': item.msg, 'data': item.data}));"></v-icon>
|
||||
<v-icon class="ms-1" icon="$copy"
|
||||
@click="copy(JSON.stringify({'type': item.type, 'createdAt': item.createdAt, 'msg': item.msg, 'data': item.data}));"></v-icon>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
@@ -72,9 +78,9 @@
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn @click="useMessageStore().deleteAllMessages()" color="error">Alle Löschen</v-btn>
|
||||
<v-btn @click="addTestMessage()" color="warning">Test Nachricht</v-btn>
|
||||
<v-btn @click="isActive.value = false">Close</v-btn>
|
||||
<v-btn @click="useMessageStore().deleteAllMessages()" color="error">{{$t('Delete_All')}}</v-btn>
|
||||
<v-btn @click="addTestMessage()" color="warning">{{$t('Add')}}</v-btn>
|
||||
<v-btn @click="isActive.value = false">{{ $t('Close')}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -84,19 +90,21 @@
|
||||
<v-dialog v-model="showDetailDialog" max-width="50vw">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
Nachricht Details <small>{{ DateTime.fromSeconds(detailItem.createdAt).toLocaleString(DateTime.DATETIME_MED) }}</small>
|
||||
{{$t('Created')}} <small>{{ DateTime.fromSeconds(detailItem.createdAt).toLocaleString(DateTime.DATETIME_MED) }}</small>
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-label>Typ</v-label>
|
||||
<v-label>{{ $t('Type')}}</v-label>
|
||||
<br/>
|
||||
<v-chip :color="detailItem.type">{{ detailItem.type }}</v-chip>
|
||||
<br/>
|
||||
|
||||
<v-label class="mt-2">Nachricht</v-label>
|
||||
<p>{{ detailItem.msg }}</p>
|
||||
<v-label class="mt-2">{{$t('Messages')}}</v-label>
|
||||
<br/>
|
||||
<b v-if="detailItem.msg.title">{{ detailItem.msg.title }}<br/></b>
|
||||
<span class="text-pre">{{ detailItem.msg.text }}</span>
|
||||
|
||||
<v-label class="mt-2">Data</v-label>
|
||||
<v-label class="mt-2">{{ $t('Information')}}</v-label>
|
||||
<pre style="white-space: pre-wrap;" v-if="detailItem.data != null">{{ detailItem.data }}</pre>
|
||||
</v-card-text>
|
||||
|
||||
@@ -118,8 +126,14 @@ import {computed, ref} from 'vue'
|
||||
import {Message, MessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {DateTime} from "luxon";
|
||||
import {useClipboard} from "@vueuse/core";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const {copy} = useClipboard()
|
||||
const {t} = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
activator: {default: 'parent'}
|
||||
})
|
||||
|
||||
/**
|
||||
* loads messages from store and filters them according to selected message types
|
||||
@@ -137,10 +151,10 @@ const displayItems = computed(() => {
|
||||
const sortBy = ref([{key: 'createdAt', order: 'desc'}])
|
||||
const search = ref('')
|
||||
const tableHeaders = ref([
|
||||
{title: 'Typ', key: 'type'},
|
||||
{title: 'Erstellt', key: 'createdAt'},
|
||||
{title: 'Nachricht', key: 'msg'},
|
||||
{title: 'Actions', key: 'actions', align: 'end'},
|
||||
{title: t('Type'), key: 'type'},
|
||||
{title: t('Created'), key: 'createdAt'},
|
||||
{title: t('Message'), key: 'msg'},
|
||||
{title: t('Actions'), key: 'actions', align: 'end'},
|
||||
])
|
||||
const typeFilter = ref([MessageType.SUCCESS, MessageType.INFO, MessageType.WARNING, MessageType.ERROR])
|
||||
const detailItem = ref({} as Message)
|
||||
@@ -151,7 +165,7 @@ const showDetailDialog = ref(false)
|
||||
*/
|
||||
function addTestMessage() {
|
||||
let types = [MessageType.SUCCESS, MessageType.ERROR, MessageType.INFO, MessageType.WARNING]
|
||||
useMessageStore().addMessage(types[Math.floor(Math.random() * types.length)], `Lorem Ipsum ${Math.random() * 1000}`, 5000, {json: "data", 'msg': 'whatever', data: 1})
|
||||
useMessageStore().addMessage(types[Math.floor(Math.random() * types.length)], {title: 'Test', text: `Lorem Ipsum ${Math.random() * 1000}`}, 5000, {json: "data", 'msg': 'whatever', data: 1})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -30,12 +30,13 @@ import SupermarketEditor from "@/components/model_editors/SupermarketEditor.vue"
|
||||
import SupermarketCategoryEditor from "@/components/model_editors/SupermarketCategoryEditor.vue";
|
||||
import PropertyTypeEditor from "@/components/model_editors/PropertyTypeEditor.vue";
|
||||
import AutomationEditor from "@/components/model_editors/AutomationEditor.vue";
|
||||
import {EditorSupportedModels} from "@/types/Models";
|
||||
|
||||
const emit = defineEmits(['create', 'save', 'delete'])
|
||||
|
||||
const props = defineProps({
|
||||
model: {
|
||||
type: String as PropType<'UnitConversion' | 'AccessToken'| 'InviteLink' | 'UserSpace' | 'MealType' | 'Property' | 'Food' | 'Supermarket' | 'SupermarketCategory' | 'PropertyType' | 'Automation'>,
|
||||
type: String as PropType<EditorSupportedModels>,
|
||||
required: true,
|
||||
},
|
||||
item: {default: null},
|
||||
|
||||
@@ -1,29 +1,35 @@
|
||||
<template>
|
||||
<v-snackbar
|
||||
v-model="showSnackbar"
|
||||
:timer="true"
|
||||
:timeout="visibleMessage.showTimeout"
|
||||
:color="visibleMessage.type"
|
||||
:vertical="vertical"
|
||||
:location="location"
|
||||
multi-line
|
||||
>
|
||||
<small>{{ DateTime.fromSeconds(visibleMessage.createdAt).toLocaleString(DateTime.DATETIME_MED) }}</small> <br/>
|
||||
{{ visibleMessage.msg }}
|
||||
<div>
|
||||
<v-snackbar
|
||||
v-model="showSnackbar"
|
||||
:timer="true"
|
||||
:timeout="visibleMessage.showTimeout"
|
||||
:color="visibleMessage.type"
|
||||
:vertical="props.vertical"
|
||||
:location="props.location"
|
||||
multi-line
|
||||
>
|
||||
<small>{{ DateTime.fromSeconds(visibleMessage.createdAt).toLocaleString(DateTime.DATETIME_MED) }}</small> <br/>
|
||||
<h3 v-if="visibleMessage.msg.title">{{ visibleMessage.msg.title }}</h3>
|
||||
<span class="text-pre">{{ visibleMessage.msg.text }}</span>
|
||||
|
||||
<template v-slot:actions>
|
||||
<template v-slot:actions>
|
||||
|
||||
<v-btn ref="ref_btn_view">View</v-btn>
|
||||
<v-btn variant="text" @click="removeItem()">
|
||||
<span v-if="useMessageStore().snackbarQueue.length > 1">Next ({{ useMessageStore().snackbarQueue.length - 1 }})</span>
|
||||
<span v-else>Close</span>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
|
||||
<message-list-dialog :activator="viewMessageDialogBtn"></message-list-dialog>
|
||||
</div>
|
||||
|
||||
<v-btn variant="text">View <message-list-dialog></message-list-dialog> </v-btn>
|
||||
<v-btn variant="text" @click="removeItem()">
|
||||
<span v-if="useMessageStore().snackbarQueue.length > 1">Next ({{ useMessageStore().snackbarQueue.length - 1 }})</span>
|
||||
<span v-else>Close</span>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import {ref, useTemplateRef} from 'vue'
|
||||
import {Message, useMessageStore} from "@/stores/MessageStore";
|
||||
import {DateTime} from "luxon";
|
||||
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
||||
@@ -49,6 +55,8 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
// ref to open message list dialog
|
||||
const viewMessageDialogBtn = useTemplateRef('ref_btn_view')
|
||||
// ID of message timeout currently running
|
||||
const timeoutId = ref(-1)
|
||||
// currently visible message
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"DeleteShoppingConfirm": "",
|
||||
"Delete_Food": "",
|
||||
"Delete_Keyword": "",
|
||||
"Deleted": "",
|
||||
"Description": "",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"DeleteShoppingConfirm": "Сигурни ли сте, че искате да премахнете цялата {food} от списъка за пазаруване?",
|
||||
"Delete_Food": "Изтриване на храна",
|
||||
"Delete_Keyword": "Изтриване на ключова дума",
|
||||
"Deleted": "",
|
||||
"Description": "Описание",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"Delete_All": "",
|
||||
"Delete_Food": "",
|
||||
"Delete_Keyword": "Esborreu paraula clau",
|
||||
"Deleted": "",
|
||||
"Description": "",
|
||||
"Description_Replace": "Substituïu descripció",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"Delete_All": "Smazat vše",
|
||||
"Delete_Food": "Smazat potravinu",
|
||||
"Delete_Keyword": "Smazat štítek",
|
||||
"Deleted": "",
|
||||
"Description": "Popis",
|
||||
"Description_Replace": "Nahraď popis",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"DeleteShoppingConfirm": "Er du sikker på at du vil fjerne {food} fra indkøbsliste?",
|
||||
"Delete_Food": "Slet mad",
|
||||
"Delete_Keyword": "Slet nøgleord",
|
||||
"Deleted": "",
|
||||
"Description": "Beskrivelse",
|
||||
"Description_Replace": "Erstat beskrivelse",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"Delete_All": "Alles löschen",
|
||||
"Delete_Food": "Lebensmittel löschen",
|
||||
"Delete_Keyword": "Schlagwort löschen",
|
||||
"Deleted": "Gelöscht",
|
||||
"Description": "Beschreibung",
|
||||
"Description_Replace": "Beschreibung ersetzen",
|
||||
"DeviceSettings": "Geräte Einstellungen",
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
"DeleteShoppingConfirm": "Θέλετε σίγουρα να αφαιρέσετε τα {food} από τη λίστα αγορών;",
|
||||
"Delete_Food": "Διαγραφή φαγητού",
|
||||
"Delete_Keyword": "Διαγραφή λέξης-κλειδί",
|
||||
"Deleted": "",
|
||||
"Description": "Περιγραφή",
|
||||
"Description_Replace": "Αλλαγή περιγραφής",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"Delete_All": "Delete all",
|
||||
"Delete_Food": "Delete Food",
|
||||
"Delete_Keyword": "Delete Keyword",
|
||||
"Deleted": "Deleted",
|
||||
"Description": "Description",
|
||||
"Description_Replace": "Description Replace",
|
||||
"DeviceSettings": "Device Settings",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"Delete_All": "Borrar todo",
|
||||
"Delete_Food": "Eliminar Ingrediente",
|
||||
"Delete_Keyword": "Eliminar palabra clave",
|
||||
"Deleted": "",
|
||||
"Description": "Descripción",
|
||||
"Description_Replace": "Reemplazar Descripción",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"DeleteConfirmQuestion": "",
|
||||
"Delete_Food": "Poista ruoka",
|
||||
"Delete_Keyword": "Poista avainsana",
|
||||
"Deleted": "",
|
||||
"Description": "Kuvaus",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"Delete_All": "Supprimer tout",
|
||||
"Delete_Food": "Supprimer l’aliment",
|
||||
"Delete_Keyword": "Supprimer le mot-clé",
|
||||
"Deleted": "",
|
||||
"Description": "Description",
|
||||
"Description_Replace": "Remplacer la Description",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"Delete_All": "מחק הכל",
|
||||
"Delete_Food": "מחק אוכל",
|
||||
"Delete_Keyword": "מחר מילת מפתח",
|
||||
"Deleted": "",
|
||||
"Description": "תיאור",
|
||||
"Description_Replace": "החלפת תיאור",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"DeleteShoppingConfirm": "Biztos, hogy az összes {food}-t el akarja távolítani a bevásárlólistáról?",
|
||||
"Delete_Food": "Alapanyag törlése",
|
||||
"Delete_Keyword": "Kulcsszó törlése",
|
||||
"Deleted": "",
|
||||
"Description": "Megnevezés",
|
||||
"Description_Replace": "Megnevezés csere",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"DeleteConfirmQuestion": "",
|
||||
"Delete_Food": "Ջնջել սննդամթերքը",
|
||||
"Delete_Keyword": "Ջնջել բանալի բառը",
|
||||
"Deleted": "",
|
||||
"Description": "Նկարագրություն",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
"DeleteShoppingConfirm": "",
|
||||
"Delete_Food": "",
|
||||
"Delete_Keyword": "Hapus Kata Kunci",
|
||||
"Deleted": "",
|
||||
"Description": "",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"Delete_All": "",
|
||||
"Delete_Food": "",
|
||||
"Delete_Keyword": "",
|
||||
"Deleted": "",
|
||||
"Description": "",
|
||||
"Description_Replace": "",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
"DeleteShoppingConfirm": "Sei sicuro di voler rimuovere tutto {food} dalla lista della spesa?",
|
||||
"Delete_Food": "Elimina alimento",
|
||||
"Delete_Keyword": "Elimina parola chiave",
|
||||
"Deleted": "",
|
||||
"Description": "Descrizione",
|
||||
"Description_Replace": "Sostituisci descrizione",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"DeleteShoppingConfirm": "",
|
||||
"Delete_Food": "",
|
||||
"Delete_Keyword": "Ištrinti raktažodį",
|
||||
"Deleted": "",
|
||||
"Description": "",
|
||||
"Description_Replace": "Pakeisti aprašymą",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
"DeleteShoppingConfirm": "Er du sikker på at du fjerne alle {food} fra handlelisten?",
|
||||
"Delete_Food": "Slett Matrett",
|
||||
"Delete_Keyword": "Slett nøkkelord",
|
||||
"Deleted": "",
|
||||
"Description": "Beskrivelse",
|
||||
"Description_Replace": "Erstatt beskrivelse",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
"DeleteShoppingConfirm": "Weet je zeker dat je {food} van de boodschappenlijst wil verwijderen?",
|
||||
"Delete_Food": "Verwijder Eten",
|
||||
"Delete_Keyword": "Verwijder Etiket",
|
||||
"Deleted": "",
|
||||
"Description": "Beschrijving",
|
||||
"Description_Replace": "Vervang beschrijving",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"Delete_All": "Usuń wszystko",
|
||||
"Delete_Food": "Usuń żywność",
|
||||
"Delete_Keyword": "Usuń słowo kluczowe",
|
||||
"Deleted": "",
|
||||
"Description": "Opis",
|
||||
"Description_Replace": "Zmień opis",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"DeleteShoppingConfirm": "Tem a certeza que pretende remover toda {food} da sua lista de compras?",
|
||||
"Delete_Food": "Eliminar comida",
|
||||
"Delete_Keyword": "Eliminar Palavra Chave",
|
||||
"Deleted": "",
|
||||
"Description": "Descrição",
|
||||
"Description_Replace": "Substituir descrição",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"Delete_All": "Excluir tudo",
|
||||
"Delete_Food": "Deletar Comida",
|
||||
"Delete_Keyword": "Deletar palavra-chave",
|
||||
"Deleted": "",
|
||||
"Description": "Descrição",
|
||||
"Description_Replace": "Substituir Descrição",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
"DeleteShoppingConfirm": "Sunteți sigur că doriți să eliminați toate {food} din lista de cumpărături?",
|
||||
"Delete_Food": "Ștergere mâncare",
|
||||
"Delete_Keyword": "Ștergere cuvânt cheie",
|
||||
"Deleted": "",
|
||||
"Description": "Descriere",
|
||||
"Description_Replace": "Înlocuire descripție",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"DeleteShoppingConfirm": "Вы уверены, что хотите удалить все {food} из вашего списка покупок?",
|
||||
"Delete_Food": "Удалить элемент",
|
||||
"Delete_Keyword": "Удалить ключевое слово",
|
||||
"Deleted": "",
|
||||
"Description": "Описание",
|
||||
"Description_Replace": "Изменить описание",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"DeleteShoppingConfirm": "Si prepričan/a, da želiš odstraniti VSO {food} iz nakupovalnega listka?",
|
||||
"Delete_Food": "Izbriši hrano",
|
||||
"Delete_Keyword": "Izbriši ključno besedo",
|
||||
"Deleted": "",
|
||||
"Description": "Opis",
|
||||
"Description_Replace": "Zamenjaj Opis",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"Delete_All": "Radera alla",
|
||||
"Delete_Food": "Ta bort livsmedel",
|
||||
"Delete_Keyword": "Ta bort nyckelord",
|
||||
"Deleted": "",
|
||||
"Description": "Beskrivning",
|
||||
"Description_Replace": "Ersätt beskrivning",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"Delete_All": "Tümünü sil",
|
||||
"Delete_Food": "Yiyeceği Sil",
|
||||
"Delete_Keyword": "Anahtar Kelimeyi Sil",
|
||||
"Deleted": "",
|
||||
"Description": "Açıklama",
|
||||
"Description_Replace": "Açıklama Değiştir",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"DeleteShoppingConfirm": "Ви впевнені, що хочете видалити {food} з вашого списку покупок?",
|
||||
"Delete_Food": "Видалити Їжу",
|
||||
"Delete_Keyword": "Видалити Ключове слово",
|
||||
"Deleted": "",
|
||||
"Description": "Опис",
|
||||
"Description_Replace": "Замінити Опис",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"Delete_All": "全部删除",
|
||||
"Delete_Food": "删除食物",
|
||||
"Delete_Keyword": "删除关键词",
|
||||
"Deleted": "",
|
||||
"Description": "描述",
|
||||
"Description_Replace": "替换描述",
|
||||
"DeviceSettings": "",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"Date": "",
|
||||
"Delete": "",
|
||||
"DeleteConfirmQuestion": "",
|
||||
"Deleted": "",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
"Download": "",
|
||||
|
||||
@@ -9,21 +9,22 @@
|
||||
<v-list>
|
||||
|
||||
<v-list-item
|
||||
v-for="model in [TFood, TUnit, TKeyword,TSupermarket, TSupermarketCategory, TPropertyType, TUnitConversion, TAutomation, TUserFile, TCookLog, TViewLog]"
|
||||
:to="{name: 'ModelListPage', params: {model: model.name}}"
|
||||
v-for="m in [TFood, TUnit, TKeyword,TSupermarket, TSupermarketCategory, TPropertyType, TUnitConversion, TAutomation, TUserFile, TCookLog, TViewLog]"
|
||||
@click="changeModel(m)"
|
||||
:active="m.name == genericModel.model.name"
|
||||
>
|
||||
<template #prepend><v-icon :icon="model.icon"></v-icon> </template>
|
||||
{{ $t(model.localizationKey) }}
|
||||
<template #prepend><v-icon :icon="m.icon"></v-icon> </template>
|
||||
{{ $t(m.localizationKey) }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<i :class="genericModel.model.icon"></i>
|
||||
{{ $t(genericModel.model.localizationKey) }}</span>
|
||||
<v-btn class="float-right" icon="$create" color="create" >
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<model-edit-dialog :close-after-create="false" :model="model" @create="loadItems({tablePage, tablePageSize})"></model-edit-dialog>
|
||||
</v-btn>
|
||||
<v-btn class="float-right" icon="$create" color="create">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<model-edit-dialog :close-after-create="false" :model="model" @create="loadItems({tablePage, tablePageSize})"></model-edit-dialog>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -55,7 +56,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
import {nextTick, onBeforeMount, onMounted, ref, watch} from "vue";
|
||||
import {nextTick, onBeforeMount, onMounted, PropType, ref, watch} from "vue";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {
|
||||
@@ -69,18 +70,25 @@ import {
|
||||
TSupermarket,
|
||||
TUnitConversion,
|
||||
TAutomation,
|
||||
TUserFile, TCookLog, TViewLog
|
||||
TUserFile, TCookLog, TViewLog, Model, EditorSupportedModels
|
||||
} from "@/types/Models";
|
||||
import {VDataTable} from "vuetify/components";
|
||||
import {useUrlSearchParams} from "@vueuse/core";
|
||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
type VDataTableProps = InstanceType<typeof VDataTable>['$props']
|
||||
|
||||
const {t} = useI18n()
|
||||
const params = useUrlSearchParams('history', {initialValue:{page:"1", pageSize: "10"}})
|
||||
const router = useRouter()
|
||||
|
||||
const params = useUrlSearchParams('history', {initialValue: {page: "1", pageSize: "10"}})
|
||||
|
||||
const props = defineProps({
|
||||
model: {type: String, default: 'Food'},
|
||||
model: {
|
||||
type: String as PropType<EditorSupportedModels>,
|
||||
default: 'Food'
|
||||
},
|
||||
})
|
||||
|
||||
// table config
|
||||
@@ -90,7 +98,7 @@ const itemsPerPageOptions = [
|
||||
{value: 50, title: '50'},
|
||||
]
|
||||
|
||||
const tableHeaders : VDataTableProps['headers'] = [
|
||||
const tableHeaders: VDataTableProps['headers'] = [
|
||||
{title: t('Name'), key: 'name'},
|
||||
{title: t('Category'), key: 'supermarketCategory.name'},
|
||||
{title: t('Actions'), key: 'action', align: 'end'},
|
||||
@@ -98,7 +106,7 @@ const tableHeaders : VDataTableProps['headers'] = [
|
||||
|
||||
const tablePage = ref(1)
|
||||
const tablePageInitialized = ref(false) // TODO workaround until vuetify bug is fixed
|
||||
const tablePageSize = ref(params.pageSize)
|
||||
const tablePageSize = ref(10)
|
||||
|
||||
const tableShowSelect = ref(true)
|
||||
|
||||
@@ -111,17 +119,21 @@ const searchQuery = ref('')
|
||||
const genericModel = ref({} as GenericModel)
|
||||
|
||||
|
||||
// watch for changes to the prop in case its changed
|
||||
// when navigating to ModelListPage from ModelListPage with a different model lifecycle hooks are not called so watch for change here
|
||||
watch(() => props.model, () => {
|
||||
console.log('loading model ', props.model)
|
||||
console.log('PAGE SIZE in watch params is ', params.pageSize)
|
||||
genericModel.value = getGenericModelFromString(props.model, t)
|
||||
loadItems({page: tablePage, itemsPerPage: tablePageSize})
|
||||
loadItems({page: 1, itemsPerPage: Number(params.pageSize)})
|
||||
})
|
||||
|
||||
/**
|
||||
* select model class before mount because template renders (and requests item load) before onMounted is called
|
||||
*/
|
||||
onBeforeMount(() => {
|
||||
// TODO this whole params thing is strange, properly review the code once vuetify fixes their table (see below)
|
||||
if (Number(params.pageSize) != tablePageSize.value) {
|
||||
tablePageSize.value = Number(params.pageSize)
|
||||
}
|
||||
try {
|
||||
genericModel.value = getGenericModelFromString(props.model, t)
|
||||
} catch (Error) {
|
||||
@@ -130,27 +142,48 @@ onBeforeMount(() => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* load items from API whenever the table calls for it
|
||||
* parameters defined by vuetify
|
||||
* @param page
|
||||
* @param itemsPerPage
|
||||
* @param search
|
||||
* @param sortBy
|
||||
* @param groupBy
|
||||
*/
|
||||
function loadItems({page, itemsPerPage, search, sortBy, groupBy}) {
|
||||
console.log('load items called', page, params.page, itemsPerPage, params.pageSize)
|
||||
loading.value = true
|
||||
// TODO workaround for initial page bug see https://github.com/vuetifyjs/vuetify/issues/17966
|
||||
if(page == 1 && Number(params.page) > 1 && !tablePageInitialized.value){
|
||||
page = params.page
|
||||
if (page == 1 && Number(params.page) > 1 && !tablePageInitialized.value) {
|
||||
page = Number(params.page)
|
||||
}
|
||||
tablePageInitialized.value = true
|
||||
|
||||
params.page = page
|
||||
params.pageSize = itemsPerPage
|
||||
genericModel.value.list({page: page, pageSize: itemsPerPage, query: search}).then(r => {
|
||||
genericModel.value.list({page: page, pageSize: itemsPerPage, query: search}).then((r: any) => {
|
||||
items.value = r.results
|
||||
itemCount.value = r.count
|
||||
tablePage.value = page // TODO remove once page bug is fixed
|
||||
}).catch((err: any) => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
|
||||
|
||||
nextTick(() => {
|
||||
console.log('setting parameters to ', page.toString(), itemsPerPage.toString())
|
||||
params.page = page.toString()
|
||||
params.pageSize = itemsPerPage.toString()
|
||||
})
|
||||
|
||||
tablePage.value = page // TODO remove once page bug is fixed
|
||||
})
|
||||
}
|
||||
|
||||
function changeModel(m: Model) {
|
||||
tablePage.value = 1
|
||||
router.push({name: 'ModelListPage', params: {model: m.name}})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -2,6 +2,8 @@ import {acceptHMRUpdate, defineStore} from 'pinia'
|
||||
import {ref} from "vue";
|
||||
import {useStorage} from "@vueuse/core";
|
||||
import {DateTime} from "luxon";
|
||||
import {ResponseError} from "@/openapi";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
/** @enum {string} different message types */
|
||||
export enum MessageType {
|
||||
@@ -13,10 +15,10 @@ export enum MessageType {
|
||||
|
||||
/** @enum {string} pre defined error messages */
|
||||
export enum ErrorMessageType {
|
||||
FETCH_ERROR = 'Fetch Error',
|
||||
UPDATE_ERROR = 'Update Error',
|
||||
CREATE_ERROR = 'Update Error',
|
||||
DELETE_ERROR = 'Update Error',
|
||||
FETCH_ERROR = 'Fehler beim Laden',
|
||||
UPDATE_ERROR = 'Fehler beim Aktualisieren',
|
||||
DELETE_ERROR = 'Fehler beim Löschen',
|
||||
CREATE_ERROR = 'Fehler beim Erstellen',
|
||||
}
|
||||
|
||||
/** @enum {MessageType} prepared messages */
|
||||
@@ -26,6 +28,14 @@ export enum PreparedMessage {
|
||||
DELETE_SUCCESS = 'DELETE_SUCCESS',
|
||||
}
|
||||
|
||||
/**
|
||||
* structured message type
|
||||
*/
|
||||
export interface StructuredMessage {
|
||||
title: string
|
||||
text: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Type Message holding all required contents of a message
|
||||
*/
|
||||
@@ -33,17 +43,20 @@ export class Message {
|
||||
type = {} as MessageType
|
||||
createdAt = -1
|
||||
showTimeout = 0
|
||||
msg = ""
|
||||
msg = {} as StructuredMessage
|
||||
data = {} as any
|
||||
code = ''
|
||||
|
||||
constructor(type: MessageType, msg: string, showTimeout?: number, data?: any) {
|
||||
constructor(type: MessageType, msg: string | StructuredMessage, showTimeout?: number, data?: any) {
|
||||
if (typeof showTimeout === 'undefined') {
|
||||
showTimeout = 0
|
||||
}
|
||||
if (typeof data === 'undefined') {
|
||||
data = {}
|
||||
}
|
||||
if (typeof msg === 'string') {
|
||||
msg = {title: '', text: msg} as StructuredMessage
|
||||
}
|
||||
|
||||
this.type = type
|
||||
this.msg = msg
|
||||
@@ -61,14 +74,19 @@ export const useMessageStore = defineStore('message_store', () => {
|
||||
let messages = useStorage('LOCAL_MESSAGES', [] as Message[])
|
||||
let snackbarQueue = ref([] as Message[])
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
/**
|
||||
* Add a message to the message store. If showTimeout is greater than 0 it is also added to the display queue.
|
||||
* @param {MessageType} type type of message
|
||||
* @param {String} msg message text
|
||||
* @param {String|StructuredMessage} msg message text or structured message
|
||||
* @param {number} showTimeout optional number of ms to show message to user, set to 0 or leave undefined for silent message
|
||||
* @param {string} data optional additional data only shown in log
|
||||
*/
|
||||
function addMessage(type: MessageType, msg: string, showTimeout?: number, data?: any) {
|
||||
function addMessage(type: MessageType, msg: string | StructuredMessage, showTimeout?: number, data?: any) {
|
||||
if (typeof msg == 'string') {
|
||||
msg = {title: '', text: msg} as StructuredMessage
|
||||
}
|
||||
let message = new Message(type, msg, showTimeout, data)
|
||||
|
||||
messages.value.push(message)
|
||||
@@ -79,27 +97,67 @@ export const useMessageStore = defineStore('message_store', () => {
|
||||
|
||||
/**
|
||||
* shorthand function to quickly add an error message
|
||||
* automatically show additional information when given supported error types (e.g. ResponseError)
|
||||
* @param errorType pre defined error type
|
||||
* @param data optional error data
|
||||
*/
|
||||
function addError(errorType: ErrorMessageType | string, data?: any) {
|
||||
addMessage(MessageType.ERROR, errorType, 7000, data)
|
||||
if (data instanceof ResponseError) {
|
||||
let messageText = ""
|
||||
messageText += `URL: ${data.response.url} \n\nErrors:\n`
|
||||
try {
|
||||
data.response.json().then(responseJson => {
|
||||
let flatResponseJson = flattenObject(responseJson)
|
||||
for (let key in flatResponseJson) {
|
||||
messageText += ` - ${key}: ${flatResponseJson[key]}\n`
|
||||
}
|
||||
addMessage(MessageType.ERROR, {
|
||||
title: `${errorType} - ${data.response.statusText} (${data.response.status})`,
|
||||
text: messageText
|
||||
} as StructuredMessage, 5000 + Object.keys(responseJson).length * 1500, responseJson)
|
||||
}).catch(() => {
|
||||
// if response does not contain parsable JSON or parsing fails for some other reason show generic error
|
||||
addMessage(MessageType.ERROR, {title: errorType, text: ''} as StructuredMessage, 7000, data)
|
||||
})
|
||||
} catch (e) {
|
||||
addMessage(MessageType.ERROR, {title: errorType, text: ''} as StructuredMessage, 7000, data)
|
||||
}
|
||||
} else {
|
||||
addMessage(MessageType.ERROR, {title: errorType, text: ''} as StructuredMessage, 7000, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* shorthand function to quickly add a message
|
||||
* @param preparedMessage pre defined message
|
||||
* @param data optional data to log along with the message
|
||||
*/
|
||||
function addPreparedMessage(preparedMessage: PreparedMessage) {
|
||||
function addPreparedMessage(preparedMessage: PreparedMessage, data?: any) {
|
||||
if (preparedMessage == PreparedMessage.UPDATE_SUCCESS) {
|
||||
addMessage(MessageType.SUCCESS, 'Updated Successfully', 7000, {}) // TODO localize and make more useful ?
|
||||
}
|
||||
if (preparedMessage == PreparedMessage.CREATE_SUCCESS) {
|
||||
addMessage(MessageType.SUCCESS, 'Created Successfully', 7000, {})
|
||||
addMessage(MessageType.SUCCESS, {title: t('Updated'), text: ''} as StructuredMessage, 6000, data)
|
||||
}
|
||||
if (preparedMessage == PreparedMessage.DELETE_SUCCESS) {
|
||||
addMessage(MessageType.SUCCESS, 'Deleted Successfully', 7000, {})
|
||||
addMessage(MessageType.SUCCESS, {title: t('Deleted'), text: ''} as StructuredMessage, 6000, data)
|
||||
}
|
||||
if (preparedMessage == PreparedMessage.CREATE_SUCCESS) {
|
||||
addMessage(MessageType.SUCCESS, {title: t('Created'), text: ''} as StructuredMessage, 6000, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* recursively flatten any multi level object to a flat object with previously nested keys seperated by dots
|
||||
* @param obj object to flatten
|
||||
* @param keyPrefix key prefix for recursive calls to build structure
|
||||
*/
|
||||
function flattenObject(obj: any, keyPrefix = '') {
|
||||
return Object.keys(obj).reduce((acc, key) => {
|
||||
if (typeof obj[key] === 'object') {
|
||||
Object.assign(acc, flattenObject(obj[key], (keyPrefix.length ? keyPrefix + '.' : '') + key))
|
||||
} else {
|
||||
acc[keyPrefix] = obj[key]
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
import {
|
||||
ApiApi,
|
||||
Keyword as IKeyword,
|
||||
Food as IFood,
|
||||
RecipeOverview as IRecipeOverview,
|
||||
Recipe as IRecipe,
|
||||
Unit as IUnit,
|
||||
MealType as IMealType,
|
||||
User as IUser,
|
||||
FoodInheritField as IFoodInheritField,
|
||||
SupermarketCategory as ISupermarketCategory,
|
||||
PropertyType as IPropertyType, ApiFoodListRequest, ApiUnitListRequest,
|
||||
} from "@/openapi";
|
||||
import { ApiApi } from "@/openapi";
|
||||
import {VDataTable} from "vuetify/components";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
type VDataTableProps = InstanceType<typeof VDataTable>['$props']
|
||||
|
||||
@@ -57,7 +44,7 @@ type ModelTableHeaders = {
|
||||
/**
|
||||
* custom type containing all attributes needed by the generic model system to properly handle all functions
|
||||
*/
|
||||
type Model = {
|
||||
export type Model = {
|
||||
name: string,
|
||||
localizationKey: string,
|
||||
icon: string,
|
||||
@@ -75,6 +62,8 @@ type Model = {
|
||||
}
|
||||
export let SUPPORTED_MODELS = new Map<string, Model>()
|
||||
|
||||
export type EditorSupportedModels = 'UnitConversion' | 'AccessToken' | 'InviteLink' | 'UserSpace' | 'MealType' | 'Property' | 'Food' | 'Supermarket' | 'SupermarketCategory' | 'PropertyType' | 'Automation'
|
||||
|
||||
export const TFood = {
|
||||
name: 'Food',
|
||||
localizationKey: 'Food',
|
||||
|
||||
Reference in New Issue
Block a user