mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 04:10:06 -05:00
usable books page
This commit is contained in:
58
vue3/src/components/display/BookEntryCard.vue
Normal file
58
vue3/src/components/display/BookEntryCard.vue
Normal 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>
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user