mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
first draft of model list view and edit pages
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item :to="{ name: 'view_settings', params: {} }"><template #prepend><v-icon icon="fa-solid fa-sliders"></v-icon></template>Settings</v-list-item>
|
||||
<v-list-item :to="{ name: 'ModelListPage', params: {} }"><template #prepend><v-icon icon="fa-solid fa-folder-tree"></v-icon></template>Database</v-list-item>
|
||||
<v-list-item><template #prepend><v-icon icon="fa-solid fa-user-shield"></v-icon></template>Admin</v-list-item>
|
||||
<v-list-item><template #prepend><v-icon icon="fa-solid fa-question"></v-icon></template>Help</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
|
||||
@@ -24,6 +24,8 @@ import SpaceSettings from "@/components/settings/SpaceSettings.vue";
|
||||
import SpaceMemberSettings from "@/components/settings/SpaceMemberSettings.vue";
|
||||
import UserSpaceSettings from "@/components/settings/UserSpaceSettings.vue";
|
||||
import ApiSettings from "@/components/settings/ApiSettings.vue";
|
||||
import ModelListPage from "@/pages/ModelListPage.vue";
|
||||
import ModelEditPage from "@/pages/ModelEditPage.vue";
|
||||
|
||||
const routes = [
|
||||
{path: '/', component: StartPage, name: 'view_home'},
|
||||
@@ -46,6 +48,10 @@ const routes = [
|
||||
{path: '/books', component: ShoppingListPage, name: 'view_books'},
|
||||
{path: '/recipe/:id', component: RecipeViewPage, name: 'view_recipe', props: true},
|
||||
{path: '/recipe/edit/:recipe_id', component: RecipeEditPage, name: 'edit_recipe', props: true},
|
||||
|
||||
{path: '/list/:model?', component: ModelListPage, props: true, name: 'ModelListPage'},
|
||||
{path: '/edit/:model?/:id', component: ModelEditPage, props: true, name: 'ModelEditPage'},
|
||||
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
114
vue3/src/components/model_editors/FoodEditor.vue
Normal file
114
vue3/src/components/model_editors/FoodEditor.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ $t(OBJ_LOCALIZATION_KEY) }}
|
||||
<v-btn class="float-right" icon="$close" variant="plain" @click="emit('close')" v-if="dialog"></v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form>
|
||||
<v-text-field v-model="editingObj.name"></v-text-field>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="delete" prepend-icon="$delete" v-if="isUpdate">{{ $t('Delete') }}
|
||||
<delete-confirm-dialog :object-name="objectName" @delete="deleteObject"></delete-confirm-dialog>
|
||||
</v-btn>
|
||||
<v-btn color="save" prepend-icon="$save" @click="saveObject">{{ isUpdate ? $t('Save') : $t('Create') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {ApiApi, Food} from "@/openapi";
|
||||
import DeleteConfirmDialog from "@/components/dialogs/DeleteConfirmDialog.vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
const emit = defineEmits(['create', 'save', 'delete', 'close'])
|
||||
|
||||
const props = defineProps({
|
||||
item: {type: {} as Food, required: false},
|
||||
itemId: {type: String, required: false},
|
||||
dialog: {type: Boolean, default: false}
|
||||
})
|
||||
|
||||
const OBJ_LOCALIZATION_KEY = 'Food'
|
||||
const editingObj = ref({} as Food)
|
||||
const loading = ref(false)
|
||||
|
||||
/**
|
||||
* checks if given object has ID property to determine if it needs to be updated or created
|
||||
*/
|
||||
const isUpdate = computed(() => {
|
||||
return editingObj.value.id !== undefined
|
||||
})
|
||||
|
||||
/**
|
||||
* display name for object in headers/delete dialog/...
|
||||
*/
|
||||
const objectName = computed(() => {
|
||||
return isUpdate ? `${t(OBJ_LOCALIZATION_KEY)} ${editingObj.value.token}` : `${t(OBJ_LOCALIZATION_KEY)} (${t('New')})`
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.item != null) {
|
||||
editingObj.value = props.item
|
||||
} else if(props.itemId != null){
|
||||
const api = new ApiApi()
|
||||
api.apiFoodRetrieve({id: props.itemId}).then(r => {
|
||||
editingObj.value = r
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
})
|
||||
} else {
|
||||
// functions to populate defaults for new item
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* saves the edited object in the database
|
||||
*/
|
||||
async function saveObject() {
|
||||
let api = new ApiApi()
|
||||
if (isUpdate.value) {
|
||||
api.apiFoodUpdate({id: editingObj.value.id, accessToken: editingObj.value}).then(r => {
|
||||
editingObj.value = r
|
||||
emit('save', r)
|
||||
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS)
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
|
||||
})
|
||||
} else {
|
||||
api.apiFoodCreate({accessToken: editingObj.value}).then(r => {
|
||||
editingObj.value = r
|
||||
emit('create', r)
|
||||
useMessageStore().addPreparedMessage(PreparedMessage.CREATE_SUCCESS)
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.CREATE_ERROR, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes the editing object from the database
|
||||
*/
|
||||
async function deleteObject() {
|
||||
let api = new ApiApi()
|
||||
api.apiFoodDestroy({id: editingObj.value.id}).then(r => {
|
||||
editingObj.value = {} as Food
|
||||
emit('delete', editingObj.value)
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.DELETE_ERROR, err)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
27
vue3/src/pages/ModelEditPage.vue
Normal file
27
vue3/src/pages/ModelEditPage.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<food-editor :item-id="id"></food-editor>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import FoodEditor from "@/components/model_editors/FoodEditor.vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {ApiApi, Food} from "@/openapi";
|
||||
|
||||
const props = defineProps({
|
||||
model: {type: String, default: 'Food'},
|
||||
id: {type: String, required: true},
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
80
vue3/src/pages/ModelListPage.vue
Normal file
80
vue3/src/pages/ModelListPage.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field prepend-inner-icon="$search" :label="$t('Search')" v-model="searchQuery"></v-text-field>
|
||||
<v-data-table-server
|
||||
@update:options="loadItems"
|
||||
:items="items"
|
||||
:items-length="itemCount"
|
||||
:loading="loading"
|
||||
:search="searchQuery"
|
||||
|
||||
:headers="tableHeaders"
|
||||
:items-per-page-options="itemsPerPageOptions"
|
||||
:show-select="tableShowSelect"
|
||||
>
|
||||
<template v-slot:item.action="{ item }">
|
||||
<v-btn color="edit" :to="{name: 'ModelEditPage', params: {id: item.id}}"><v-icon icon="$edit"></v-icon></v-btn>
|
||||
</template>
|
||||
</v-data-table-server>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</v-container>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {ApiApi, Food} from "@/openapi";
|
||||
import {ref} from "vue";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const {t} = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
model: {type: String, default: 'Food'},
|
||||
})
|
||||
|
||||
// table config
|
||||
const itemsPerPageOptions = [
|
||||
{value: 10, title: '10'},
|
||||
{value: 25, title: '25'},
|
||||
{value: 50, title: '50'},
|
||||
]
|
||||
|
||||
const tableHeaders = [
|
||||
{title: t('Name'), key: 'name'},
|
||||
{title: t('Category'), key: 'supermarketCategory.name'},
|
||||
{title: t('Edit'), key: 'action', align: 'end'},
|
||||
]
|
||||
|
||||
const tableShowSelect = ref(true)
|
||||
|
||||
// data
|
||||
const loading = ref(false);
|
||||
const items = ref([] as Food[])
|
||||
const itemCount = ref(0)
|
||||
const searchQuery = ref('')
|
||||
|
||||
function loadItems({page, itemsPerPage, search, sortBy, groupBy}) {
|
||||
const api = new ApiApi()
|
||||
loading.value = true
|
||||
api.apiFoodList({page: page, pageSize: itemsPerPage, query: search}).then(r => {
|
||||
items.value = r.results
|
||||
itemCount.value = r.count
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user