Add to Shopping List Dialog

This commit is contained in:
Kuchenpirat 2025-07-30 10:45:44 +00:00
commit 0dbb837dff

View file

@ -51,7 +51,7 @@
<BaseDialog <BaseDialog
v-if="shoppingListIngredientDialog" v-if="shoppingListIngredientDialog"
v-model="dialog" v-model="dialog"
:title="selectedShoppingList ? selectedShoppingList.name : $t('recipe.add-to-list')" :title="selectedShoppingList?.name || $t('recipe.add-to-list')"
:icon="$globals.icons.cartCheck" :icon="$globals.icons.cartCheck"
width="70%" width="70%"
:submit-text="$t('recipe.add-to-list')" :submit-text="$t('recipe.add-to-list')"
@ -137,7 +137,7 @@
color="secondary" color="secondary"
density="compact" density="compact"
/> />
<div :key="ingredientData.ingredient.quantity"> <div :key="`${ingredientData.ingredient.quantity || 'no-qty'}-${i}`">
<RecipeIngredientListItem <RecipeIngredientListItem
:ingredient="ingredientData.ingredient" :ingredient="ingredientData.ingredient"
:disable-amount="ingredientData.disableAmount" :disable-amount="ingredientData.disableAmount"
@ -172,7 +172,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { toRefs } from "@vueuse/core"; import { toRefs } from "@vueuse/core";
import RecipeIngredientListItem from "./RecipeIngredientListItem.vue"; import RecipeIngredientListItem from "./RecipeIngredientListItem.vue";
import { useUserApi } from "~/composables/api"; import { useUserApi } from "~/composables/api";
@ -203,61 +203,43 @@ export interface ShoppingListRecipeIngredientSection {
ingredientSections: ShoppingListIngredientSection[]; ingredientSections: ShoppingListIngredientSection[];
} }
export default defineNuxtComponent({ interface Props {
components: { recipes?: RecipeWithScale[];
RecipeIngredientListItem, shoppingLists?: ShoppingListSummary[];
}, }
props: { const props = withDefaults(defineProps<Props>(), {
modelValue: { recipes: undefined,
type: Boolean, shoppingLists: () => [],
default: false, });
},
recipes: {
type: Array as () => RecipeWithScale[],
default: undefined,
},
shoppingLists: {
type: Array as () => ShoppingListSummary[],
default: () => [],
},
},
emits: ["update:modelValue"],
setup(props, context) {
const i18n = useI18n();
const $auth = useMealieAuth();
const api = useUserApi();
const preferences = useShoppingListPreferences();
const ready = ref(false);
// v-model support const dialog = defineModel<boolean>({ default: false });
const dialog = computed({
get: () => {
return props.modelValue;
},
set: (val) => {
context.emit("update:modelValue", val);
initState();
},
});
const state = reactive({ const i18n = useI18n();
const $auth = useMealieAuth();
const api = useUserApi();
const preferences = useShoppingListPreferences();
const ready = ref(false);
const state = reactive({
shoppingListDialog: true, shoppingListDialog: true,
shoppingListIngredientDialog: false, shoppingListIngredientDialog: false,
shoppingListShowAllToggled: false, shoppingListShowAllToggled: false,
}); });
const userHousehold = computed(() => { const { shoppingListDialog, shoppingListIngredientDialog, shoppingListShowAllToggled: _shoppingListShowAllToggled } = toRefs(state);
const userHousehold = computed(() => {
return $auth.user.value?.householdSlug || ""; return $auth.user.value?.householdSlug || "";
}); });
const shoppingListChoices = computed(() => { const shoppingListChoices = computed(() => {
return props.shoppingLists.filter(list => preferences.value.viewAllLists || list.userId === $auth.user.value?.id); return props.shoppingLists.filter(list => preferences.value.viewAllLists || list.userId === $auth.user.value?.id);
}); });
const recipeIngredientSections = ref<ShoppingListRecipeIngredientSection[]>([]); const recipeIngredientSections = ref<ShoppingListRecipeIngredientSection[]>([]);
const selectedShoppingList = ref<ShoppingListSummary | null>(null); const selectedShoppingList = ref<ShoppingListSummary | null>(null);
watchEffect( watchEffect(
() => { () => {
if (shoppingListChoices.value.length === 1 && !state.shoppingListShowAllToggled) { if (shoppingListChoices.value.length === 1 && !state.shoppingListShowAllToggled) {
selectedShoppingList.value = shoppingListChoices.value[0]; selectedShoppingList.value = shoppingListChoices.value[0];
@ -267,9 +249,15 @@ export default defineNuxtComponent({
ready.value = true; ready.value = true;
} }
}, },
); );
async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) { watch(dialog, (val) => {
if (!val) {
initState();
}
});
async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
const recipeSectionMap = new Map<string, ShoppingListRecipeIngredientSection>(); const recipeSectionMap = new Map<string, ShoppingListRecipeIngredientSection>();
for (const recipe of recipes) { for (const recipe of recipes) {
if (!recipe.slug) { if (!recipe.slug) {
@ -277,7 +265,10 @@ export default defineNuxtComponent({
} }
if (recipeSectionMap.has(recipe.slug)) { if (recipeSectionMap.has(recipe.slug)) {
recipeSectionMap.get(recipe.slug).recipeScale += recipe.scale; const existingSection = recipeSectionMap.get(recipe.slug);
if (existingSection) {
existingSection.recipeScale += recipe.scale;
}
continue; continue;
} }
@ -347,19 +338,19 @@ export default defineNuxtComponent({
} }
recipeIngredientSections.value = Array.from(recipeSectionMap.values()); recipeIngredientSections.value = Array.from(recipeSectionMap.values());
} }
function initState() { function initState() {
state.shoppingListDialog = true; state.shoppingListDialog = true;
state.shoppingListIngredientDialog = false; state.shoppingListIngredientDialog = false;
state.shoppingListShowAllToggled = false; state.shoppingListShowAllToggled = false;
recipeIngredientSections.value = []; recipeIngredientSections.value = [];
selectedShoppingList.value = null; selectedShoppingList.value = null;
} }
initState(); initState();
async function openShoppingListIngredientDialog(list: ShoppingListSummary) { async function openShoppingListIngredientDialog(list: ShoppingListSummary) {
if (!props.recipes?.length) { if (!props.recipes?.length) {
return; return;
} }
@ -368,13 +359,13 @@ export default defineNuxtComponent({
await consolidateRecipesIntoSections(props.recipes); await consolidateRecipesIntoSections(props.recipes);
state.shoppingListDialog = false; state.shoppingListDialog = false;
state.shoppingListIngredientDialog = true; state.shoppingListIngredientDialog = true;
} }
function setShowAllToggled() { function setShowAllToggled() {
state.shoppingListShowAllToggled = true; state.shoppingListShowAllToggled = true;
} }
function bulkCheckIngredients(value = true) { function bulkCheckIngredients(value = true) {
recipeIngredientSections.value.forEach((recipeSection) => { recipeIngredientSections.value.forEach((recipeSection) => {
recipeSection.ingredientSections.forEach((ingSection) => { recipeSection.ingredientSections.forEach((ingSection) => {
ingSection.ingredients.forEach((ing) => { ingSection.ingredients.forEach((ing) => {
@ -382,9 +373,9 @@ export default defineNuxtComponent({
}); });
}); });
}); });
} }
async function addRecipesToList() { async function addRecipesToList() {
if (!selectedShoppingList.value) { if (!selectedShoppingList.value) {
return; return;
} }
@ -420,23 +411,7 @@ export default defineNuxtComponent({
state.shoppingListDialog = false; state.shoppingListDialog = false;
state.shoppingListIngredientDialog = false; state.shoppingListIngredientDialog = false;
dialog.value = false; dialog.value = false;
} }
return {
dialog,
preferences,
ready,
shoppingListChoices,
...toRefs(state),
addRecipesToList,
bulkCheckIngredients,
openShoppingListIngredientDialog,
setShowAllToggled,
recipeIngredientSections,
selectedShoppingList,
};
},
});
</script> </script>
<style scoped lang="css"> <style scoped lang="css">