mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-21 22:13:31 -07:00
feat: group recipe ingredients by section titles
Group ingredients under their respective section titles in the recipe page, improving organization and clarity. Added logic to compute and map section titles for used and unused ingredients. Updated related components to display grouped ingredients accordingly.
This commit is contained in:
parent
f2b71e981e
commit
a2b6cbf6ab
3 changed files with 99 additions and 15 deletions
|
@ -29,32 +29,49 @@
|
||||||
{{ activeText }}
|
{{ activeText }}
|
||||||
</p>
|
</p>
|
||||||
<v-divider class="mb-4" />
|
<v-divider class="mb-4" />
|
||||||
<v-checkbox-btn
|
<template v-if="Object.keys(groupedUnusedIngredients).length > 0">
|
||||||
v-for="ing in unusedIngredients"
|
|
||||||
:key="ing.referenceId"
|
|
||||||
v-model="activeRefs"
|
|
||||||
:value="ing.referenceId"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<RecipeIngredientHtml :markup="parseIngredientText(ing)" />
|
|
||||||
</template>
|
|
||||||
</v-checkbox-btn>
|
|
||||||
|
|
||||||
<template v-if="usedIngredients.length > 0">
|
|
||||||
<h4 class="py-3 ml-1">
|
<h4 class="py-3 ml-1">
|
||||||
{{ $t("recipe.linked-to-other-step") }}
|
{{ $t("recipe.unlinked") }}
|
||||||
|
</h4>
|
||||||
|
<template v-for="(ingredients, title) in groupedUnusedIngredients" :key="title">
|
||||||
|
<h4 v-if="title" class="py-3 ml-1 pl-4">
|
||||||
|
{{ title }}
|
||||||
</h4>
|
</h4>
|
||||||
<v-checkbox-btn
|
<v-checkbox-btn
|
||||||
v-for="ing in usedIngredients"
|
v-for="ing in ingredients"
|
||||||
:key="ing.referenceId"
|
:key="ing.referenceId"
|
||||||
v-model="activeRefs"
|
v-model="activeRefs"
|
||||||
:value="ing.referenceId"
|
:value="ing.referenceId"
|
||||||
|
class="ml-4"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<RecipeIngredientHtml :markup="parseIngredientText(ing)" />
|
<RecipeIngredientHtml :markup="parseIngredientText(ing)" />
|
||||||
</template>
|
</template>
|
||||||
</v-checkbox-btn>
|
</v-checkbox-btn>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="Object.keys(groupedUsedIngredients).length > 0">
|
||||||
|
<h4 class="py-3 ml-1">
|
||||||
|
{{ $t("recipe.linked-to-other-step") }}
|
||||||
|
</h4>
|
||||||
|
<template v-for="(ingredients, title) in groupedUsedIngredients" :key="title">
|
||||||
|
<h4 v-if="title" class="py-3 ml-1 pl-4">
|
||||||
|
{{ title }}
|
||||||
|
</h4>
|
||||||
|
<v-checkbox-btn
|
||||||
|
v-for="ing in ingredients"
|
||||||
|
:key="ing.referenceId"
|
||||||
|
v-model="activeRefs"
|
||||||
|
:value="ing.referenceId"
|
||||||
|
class="ml-4"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<RecipeIngredientHtml :markup="parseIngredientText(ing)" />
|
||||||
|
</template>
|
||||||
|
</v-checkbox-btn>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-divider />
|
<v-divider />
|
||||||
|
@ -563,6 +580,71 @@ const ingredientLookup = computed(() => {
|
||||||
}, results);
|
}, results);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Map each ingredient's referenceId to its section title
|
||||||
|
const ingredientSectionTitles = computed(() => {
|
||||||
|
const titleMap: { [key: string]: string } = {};
|
||||||
|
let currentTitle = "";
|
||||||
|
|
||||||
|
// Go through all ingredients in order
|
||||||
|
props.recipe.recipeIngredient.forEach((ingredient) => {
|
||||||
|
if (ingredient.referenceId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this ingredient has a title, update the current title
|
||||||
|
if (ingredient.title) {
|
||||||
|
currentTitle = ingredient.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the current title to this ingredient
|
||||||
|
titleMap[ingredient.referenceId] = currentTitle;
|
||||||
|
});
|
||||||
|
|
||||||
|
return titleMap;
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedUnusedIngredients = computed(() => {
|
||||||
|
const groups: { [key: string]: RecipeIngredient[] } = {};
|
||||||
|
|
||||||
|
// Group ingredients by section title
|
||||||
|
unusedIngredients.value.forEach((ingredient) => {
|
||||||
|
if (ingredient.referenceId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the section title from the mapping, or fallback to the ingredient's own title
|
||||||
|
const title = ingredientSectionTitles.value[ingredient.referenceId] || ingredient.title || "";
|
||||||
|
|
||||||
|
if (!groups[title]) {
|
||||||
|
groups[title] = [];
|
||||||
|
}
|
||||||
|
groups[title].push(ingredient);
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedUsedIngredients = computed(() => {
|
||||||
|
const groups: { [key: string]: RecipeIngredient[] } = {};
|
||||||
|
|
||||||
|
// Group ingredients by section title
|
||||||
|
usedIngredients.value.forEach((ingredient) => {
|
||||||
|
if (ingredient.referenceId === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the section title from the mapping, or fallback to the ingredient's own title
|
||||||
|
const title = ingredientSectionTitles.value[ingredient.referenceId] || ingredient.title || "";
|
||||||
|
|
||||||
|
if (!groups[title]) {
|
||||||
|
groups[title] = [];
|
||||||
|
}
|
||||||
|
groups[title].push(ingredient);
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
});
|
||||||
|
|
||||||
function getIngredientByRefId(refId: string | undefined) {
|
function getIngredientByRefId(refId: string | undefined) {
|
||||||
if (refId === undefined) {
|
if (refId === undefined) {
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -37,7 +37,7 @@ function useUnitName(unit: CreateIngredientUnit | IngredientUnit | undefined, us
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useParsedIngredientText(ingredient: RecipeIngredient, scale = 1, includeFormating = true) {
|
export function useParsedIngredientText(ingredient: RecipeIngredient, scale = 1, includeFormating = true) {
|
||||||
const { quantity, food, unit, note } = ingredient;
|
const { quantity, food, unit, note, title } = ingredient;
|
||||||
const usePluralUnit = quantity !== undefined && ((quantity || 0) * scale > 1 || (quantity || 0) * scale === 0);
|
const usePluralUnit = quantity !== undefined && ((quantity || 0) * scale > 1 || (quantity || 0) * scale === 0);
|
||||||
const usePluralFood = (!quantity) || quantity * scale > 1;
|
const usePluralFood = (!quantity) || quantity * scale > 1;
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, scale = 1,
|
||||||
const foodName = useFoodName(food || undefined, usePluralFood);
|
const foodName = useFoodName(food || undefined, usePluralFood);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: title ? sanitizeIngredientHTML(title) : undefined,
|
||||||
quantity: returnQty ? sanitizeIngredientHTML(returnQty) : undefined,
|
quantity: returnQty ? sanitizeIngredientHTML(returnQty) : undefined,
|
||||||
unit: unitName && quantity ? sanitizeIngredientHTML(unitName) : undefined,
|
unit: unitName && quantity ? sanitizeIngredientHTML(unitName) : undefined,
|
||||||
name: foodName ? sanitizeIngredientHTML(foodName) : undefined,
|
name: foodName ? sanitizeIngredientHTML(foodName) : undefined,
|
||||||
|
|
|
@ -561,6 +561,7 @@
|
||||||
"see-original-text": "See Original Text",
|
"see-original-text": "See Original Text",
|
||||||
"original-text-with-value": "Original Text: {originalText}",
|
"original-text-with-value": "Original Text: {originalText}",
|
||||||
"ingredient-linker": "Ingredient Linker",
|
"ingredient-linker": "Ingredient Linker",
|
||||||
|
"unlinked": "Not linked yet",
|
||||||
"linked-to-other-step": "Linked to other step",
|
"linked-to-other-step": "Linked to other step",
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"cook-mode": "Cook Mode",
|
"cook-mode": "Cook Mode",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue