fixed some things but page param still broken

This commit is contained in:
vabene1111
2024-10-07 21:11:21 +02:00
parent 257f4f2b5b
commit 25de4326d2
37 changed files with 232 additions and 98 deletions

View File

@@ -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>

View File

@@ -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},

View File

@@ -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

View File

@@ -68,6 +68,7 @@
"DeleteShoppingConfirm": "",
"Delete_Food": "",
"Delete_Keyword": "",
"Deleted": "",
"Description": "",
"DeviceSettings": "",
"DeviceSettingsHelp": "",

View File

@@ -65,6 +65,7 @@
"DeleteShoppingConfirm": "Сигурни ли сте, че искате да премахнете цялата {food} от списъка за пазаруване?",
"Delete_Food": "Изтриване на храна",
"Delete_Keyword": "Изтриване на ключова дума",
"Deleted": "",
"Description": "Описание",
"DeviceSettings": "",
"DeviceSettingsHelp": "",

View File

@@ -97,6 +97,7 @@
"Delete_All": "",
"Delete_Food": "",
"Delete_Keyword": "Esborreu paraula clau",
"Deleted": "",
"Description": "",
"Description_Replace": "Substituïu descripció",
"DeviceSettings": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -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",

View File

@@ -87,6 +87,7 @@
"DeleteShoppingConfirm": "Θέλετε σίγουρα να αφαιρέσετε τα {food} από τη λίστα αγορών;",
"Delete_Food": "Διαγραφή φαγητού",
"Delete_Keyword": "Διαγραφή λέξης-κλειδί",
"Deleted": "",
"Description": "Περιγραφή",
"Description_Replace": "Αλλαγή περιγραφής",
"DeviceSettings": "",

View File

@@ -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",

View File

@@ -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": "",

View File

@@ -45,6 +45,7 @@
"DeleteConfirmQuestion": "",
"Delete_Food": "Poista ruoka",
"Delete_Keyword": "Poista avainsana",
"Deleted": "",
"Description": "Kuvaus",
"DeviceSettings": "",
"DeviceSettingsHelp": "",

View File

@@ -97,6 +97,7 @@
"Delete_All": "Supprimer tout",
"Delete_Food": "Supprimer laliment",
"Delete_Keyword": "Supprimer le mot-clé",
"Deleted": "",
"Description": "Description",
"Description_Replace": "Remplacer la Description",
"DeviceSettings": "",

View File

@@ -98,6 +98,7 @@
"Delete_All": "מחק הכל",
"Delete_Food": "מחק אוכל",
"Delete_Keyword": "מחר מילת מפתח",
"Deleted": "",
"Description": "תיאור",
"Description_Replace": "החלפת תיאור",
"DeviceSettings": "",

View File

@@ -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": "",

View File

@@ -32,6 +32,7 @@
"DeleteConfirmQuestion": "",
"Delete_Food": "Ջնջել սննդամթերքը",
"Delete_Keyword": "Ջնջել բանալի բառը",
"Deleted": "",
"Description": "Նկարագրություն",
"DeviceSettings": "",
"DeviceSettingsHelp": "",

View File

@@ -77,6 +77,7 @@
"DeleteShoppingConfirm": "",
"Delete_Food": "",
"Delete_Keyword": "Hapus Kata Kunci",
"Deleted": "",
"Description": "",
"DeviceSettings": "",
"DeviceSettingsHelp": "",

View File

@@ -97,6 +97,7 @@
"Delete_All": "",
"Delete_Food": "",
"Delete_Keyword": "",
"Deleted": "",
"Description": "",
"Description_Replace": "",
"DeviceSettings": "",

View File

@@ -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": "",

View File

@@ -88,6 +88,7 @@
"DeleteShoppingConfirm": "",
"Delete_Food": "",
"Delete_Keyword": "Ištrinti raktažodį",
"Deleted": "",
"Description": "",
"Description_Replace": "Pakeisti aprašymą",
"DeviceSettings": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -58,6 +58,7 @@
"DeleteShoppingConfirm": "Вы уверены, что хотите удалить все {food} из вашего списка покупок?",
"Delete_Food": "Удалить элемент",
"Delete_Keyword": "Удалить ключевое слово",
"Deleted": "",
"Description": "Описание",
"Description_Replace": "Изменить описание",
"DeviceSettings": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -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": "",

View File

@@ -73,6 +73,7 @@
"DeleteShoppingConfirm": "Ви впевнені, що хочете видалити {food} з вашого списку покупок?",
"Delete_Food": "Видалити Їжу",
"Delete_Keyword": "Видалити Ключове слово",
"Deleted": "",
"Description": "Опис",
"Description_Replace": "Замінити Опис",
"DeviceSettings": "",

View File

@@ -95,6 +95,7 @@
"Delete_All": "全部删除",
"Delete_Food": "删除食物",
"Delete_Keyword": "删除关键词",
"Deleted": "",
"Description": "描述",
"Description_Replace": "替换描述",
"DeviceSettings": "",

View File

@@ -25,6 +25,7 @@
"Date": "",
"Delete": "",
"DeleteConfirmQuestion": "",
"Deleted": "",
"DeviceSettings": "",
"DeviceSettingsHelp": "",
"Download": "",

View File

@@ -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>

View File

@@ -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;
}, {});
}
/**

View File

@@ -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',