usable books page

This commit is contained in:
vabene1111
2025-02-15 09:50:40 +01:00
parent 09a76a2057
commit 0461c57cf3
5 changed files with 170 additions and 31 deletions

View File

@@ -0,0 +1,58 @@
<template>
<v-card :loading="loading">
<v-card-title>{{ props.recipeOverview.name }}</v-card-title>
<recipe-image height="25vh" :recipe="props.recipeOverview"></recipe-image>
<v-card-subtitle>{{ props.recipeOverview.description }}</v-card-subtitle>
<v-card-text>
<keywords-bar :keywords="props.recipeOverview.keywords"></keywords-bar>
</v-card-text>
<ingredients-table :ingredient-factor="1" v-model="ingredients" :show-checkbox="false"></ingredients-table>
</v-card>
</template>
<script setup lang="ts">
import RecipeImage from "@/components/display/RecipeImage.vue";
import {ApiApi, Ingredient, Recipe, RecipeOverview} from "@/openapi";
import {onMounted, PropType, ref} from "vue";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import IngredientsTable from "@/components/display/IngredientsTable.vue";
import {getRecipeIngredients} from "@/utils/model_utils";
import {useI18n} from "vue-i18n";
import KeywordsBar from "@/components/display/KeywordsBar.vue";
const props = defineProps({
recipeOverview: {type: {} as PropType<RecipeOverview>, required: true}
})
const {t} = useI18n()
const loading = ref(false)
const recipe = ref({} as Recipe)
const ingredients = ref([] as Ingredient[])
onMounted(() => {
loadRecipe()
})
function loadRecipe() {
let api = new ApiApi()
loading.value = true
api.apiRecipeRetrieve({id: props.recipeOverview.id!}).then(r => {
recipe.value = r
ingredients.value = getRecipeIngredients(recipe.value, t,{showStepHeaders: true})
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
}).finally(() => {
loading.value = false
})
}
</script>
<style scoped>
</style>

View File

@@ -33,7 +33,7 @@
<td colspan="5" class="font-weight-bold">{{ i.note }}</td>
</template>
<template v-else>
<td style="width: 1%; text-wrap: nowrap" class="pa-0">
<td style="width: 1%; text-wrap: nowrap" class="pa-0" v-if="showCheckbox">
<v-checkbox-btn v-model="i.checked" color="success" v-if="!i.isHeader"></v-checkbox-btn>
</td>
<td style="width: 1%; text-wrap: nowrap" class="pr-1" v-html="calculateFoodAmount(i.amount, props.ingredientFactor, useUserPreferenceStore().userSettings.useFractions)"></td>
@@ -73,6 +73,10 @@ const props = defineProps({
type: Number,
required: true,
},
showCheckbox: {
type: Boolean,
default: true
},
})
const tableHeaders = computed(() => {

View File

@@ -3,31 +3,68 @@
<v-row>
<v-col>
<v-card>
<v-card-text class="pt-2 pb-2">
<v-btn variant="flat" @click="router.go(-1)" prepend-icon="fa-solid fa-arrow-left">{{ $t('Back') }}</v-btn>
<v-card-title>{{ book.name }}
<v-btn class="float-right" variant="flat" :to="{name: 'BooksPage'}" prepend-icon="$books" v-if="mdAndUp">{{ $t('Books') }}</v-btn>
</v-card-title>
<v-card-text v-if="book.shared && book.shared.length > 0">
<v-chip-group>
<v-label class="me-2">{{ $t('shared_with') }}</v-label>
<v-chip v-for="u in book.shared">{{ u.displayName }}</v-chip>
</v-chip-group>
</v-card-text>
<v-card-text class="text-disabled">
{{ book.description }}
</v-card-text>
<v-expansion-panels v-model="toc">
<v-expansion-panel>
<v-expansion-panel-title>{{ $t('Table_of_Contents') }}</v-expansion-panel-title>
<v-expansion-panel-text>
<v-list>
<v-list-item v-for="(entry, i) in entries" :key="entry.id" @click="page = i; toc = false">
{{ entry.recipeContent.name }}
</v-list-item>
</v-list>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col>
<h2>{{ book.name }}</h2>
<p class="text-disabled">{{ book.description }}</p>
<v-col class="text-center">
<v-pagination :model-value="page + 1" @update:model-value="value => page = value - 1" :length="totalItems" @next="page = page + (mdAndUp ? 2 : 1)"
@prev="page = page - (mdAndUp ? 2 : 1)"></v-pagination>
</v-col>
</v-row>
<v-row>
<v-col cols="12" md="6">
<v-data-iterator :items="entries" :items-per-page="recipesPerPage" :page="page" @update:page="loadRecipe">
<template #default="{items}">
<v-card v-for="i in items">
<v-card-title>{{i.raw.recipeContent.name}}</v-card-title>
<v-card-subtitle>{{i.raw.recipeContent.desciption}}</v-card-subtitle>
</v-card>
<v-row>
<v-col cols="12">
<v-window v-model="page" show-arrows>
<template #next>
<v-btn icon="fa-solid fa-chevron-right" variant="plain" @click="page = page + (mdAndUp ? 2 : 1)"></v-btn>
</template>
</v-data-iterator>
<v-pagination v-model="page" :length="totalItems / recipesPerPage"></v-pagination>
<template #prev>
<v-btn icon="fa-solid fa-chevron-left" variant="plain" @click="page = page - (mdAndUp ? 2 : 1)"></v-btn>
</template>
<v-window-item v-for="(entry, i) in entries" :key="entry.id">
<v-row>
<v-col cols="12" md="6">
<book-entry-card :recipe-overview="entries[i].recipeContent"></book-entry-card>
<div class="text-center mt-1">
<span class="text-disabled">{{ i + 1 }}</span>
</div>
</v-col>
<v-col cols="6" v-if="mdAndUp && entries.length > i + 1">
<book-entry-card :recipe-overview="entries[i + 1].recipeContent"></book-entry-card>
<div class="text-center mt-1">
<span class="text-disabled">{{ i + 2 }}</span>
</div>
</v-col>
</v-row>
</v-window-item>
</v-window>
</v-col>
</v-row>
</v-container>
@@ -39,18 +76,21 @@
import {onMounted, ref} from "vue";
import {ApiApi, RecipeBook, RecipeBookEntry} from "@/openapi";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import {VDataTableUpdateOptions} from "@/vuetify";
import {useRouter} from "vue-router";
import RecipeImage from "@/components/display/RecipeImage.vue";
import {useDisplay} from "vuetify";
import BookEntryCard from "@/components/display/BookEntryCard.vue";
const props = defineProps({
bookId: {type: String, required: true},
})
const {mdAndUp} = useDisplay()
const router = useRouter()
const loading = ref(false)
const page = ref(1)
const recipesPerPage = ref(1)
const toc = ref(false)
const page = ref(0)
const totalItems = ref(0)
const book = ref({} as RecipeBook)
@@ -58,13 +98,15 @@ const entries = ref([] as RecipeBookEntry[])
onMounted(() => {
loadBook()
loadEntries({page: 1})
entries.value = []
loadEntries(1)
})
/**
* load the given book
*/
function loadBook(){
function loadBook() {
const api = new ApiApi()
loading.value = true
@@ -77,12 +119,15 @@ function loadBook(){
})
}
function loadEntries(options: VDataTableUpdateOptions){
function loadEntries(page: number) {
const api = new ApiApi()
api.apiRecipeBookEntryList({book: props.bookId, page: options.page}).then(r => {
entries.value = r.results
api.apiRecipeBookEntryList({book: props.bookId, page: page}).then(r => {
entries.value = entries.value.concat(r.results)
totalItems.value = r.count
if (r.next) {
loadEntries(page + 1)
}
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
})

View File

@@ -1,4 +1,4 @@
import {Ingredient} from "@/openapi";
import {Ingredient, Recipe} from "@/openapi";
/**
* returns a string representing an ingredient
@@ -7,21 +7,46 @@ import {Ingredient} from "@/openapi";
export function ingredientToString(ingredient: Ingredient) {
let content = []
if(ingredient == undefined){
if (ingredient == undefined) {
return ''
}
if(ingredient.amount != 0){
if (ingredient.amount != 0) {
content.push(ingredient.amount)
}
if(ingredient.unit){
if (ingredient.unit) {
content.push(ingredient.unit.name)
}
if(ingredient.food){
if (ingredient.food) {
content.push(ingredient.food.name)
}
if(ingredient.note){
if (ingredient.note) {
content.push(`(${ingredient.note})`)
}
return content.join(' ')
}
/**
* returns a list of all ingredients used by the given recipe
* @param recipe recipe to return ingredients for
* @param t useI18N object to use for translation
* @param options options object for list generation
* showStepHeaders - add steps as a header ingredient if it's configured on the step
*/
export function getRecipeIngredients(recipe: Recipe, t: any, options: { showStepHeaders: boolean } = {showStepHeaders: false}) {
let ingredients = [] as Ingredient[]
recipe.steps.forEach((step, index) => {
if (step.showAsHeader && options.showStepHeaders && recipe.steps.length > 1 && (step.ingredients.length > 0 || step.name != '')) {
ingredients.push({
amount: 0,
unit: null,
food: null,
note: (step.name !== '') ? step.name : t('Step') + ' ' + (index + 1),
isHeader: true
} as Ingredient)
}
ingredients = ingredients.concat(step.ingredients)
})
return ingredients
}