feat: Add range of dates to shopping list from meal planner (#6981)

This commit is contained in:
Michael Genson
2026-02-01 15:58:03 -06:00
committed by GitHub
parent b86de79c6f
commit af241dad57
4 changed files with 83 additions and 10 deletions

View File

@@ -91,7 +91,7 @@ const state = reactive({
shoppingListDialog: false,
menuItems: [
{
title: i18n.t("recipe.add-to-list"),
title: i18n.t("meal-plan.add-day-to-list"),
icon: $globals.icons.cartCheck,
color: undefined,
event: "shoppingList",
@@ -123,8 +123,8 @@ async function getShoppingLists() {
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
const eventHandlers: { [key: string]: () => void | Promise<any> } = {
shoppingList: () => {
getShoppingLists();
shoppingList: async () => {
await getShoppingLists();
state.shoppingListDialog = true;
},
};

View File

@@ -227,7 +227,7 @@ const currentHouseholdSlug = ref("");
const filteredShoppingLists = ref<ShoppingListSummary[]>([]);
const state = reactive({
shoppingListDialog: true,
shoppingListDialog: false,
shoppingListIngredientDialog: false,
shoppingListShowAllToggled: false,
});
@@ -249,6 +249,7 @@ watch([dialog, () => preferences.value.viewAllLists], () => {
openShoppingListIngredientDialog(selectedShoppingList.value);
}
else {
state.shoppingListDialog = true;
ready.value = true;
}
}
@@ -371,7 +372,7 @@ async function consolidateRecipesIntoSections(recipes: RecipeWithScale[]) {
}
function initState() {
state.shoppingListDialog = true;
state.shoppingListDialog = false;
state.shoppingListIngredientDialog = false;
state.shoppingListShowAllToggled = false;
recipeIngredientSections.value = [];

View File

@@ -369,7 +369,9 @@
"recipe-rules": "Recipe Rules",
"applies-to-all-days": "Applies to all days",
"applies-on-days": "Applies on {0}s",
"meal-plan-settings": "Meal Plan Settings"
"meal-plan-settings": "Meal Plan Settings",
"add-all-to-list": "Add All to List",
"add-day-to-list": "Add Day to List"
},
"migration": {
"migration-data-removed": "Migration data removed",

View File

@@ -1,5 +1,11 @@
<template>
<v-container>
<RecipeDialogAddToShoppingList
v-if="shoppingLists"
v-model="state.shoppingListDialog"
:recipes="weekRecipesWithScales"
:shopping-lists="shoppingLists"
/>
<v-menu
v-model="state.picker"
:close-on-content-click="false"
@@ -45,13 +51,23 @@
<div class="d-flex flex-wrap align-center justify-space-between mb-2">
<v-tabs style="width: fit-content;">
<v-tab :to="{ name: 'household-mealplan-planner-view', query: route.query }">
<v-tab :to="{ name: TABS.view, query: route.query }">
{{ $t('meal-plan.meal-planner') }}
</v-tab>
<v-tab :to="{ name: 'household-mealplan-planner-edit', query: route.query }">
<v-tab :to="{ name: TABS.edit, query: route.query }">
{{ $t('general.edit') }}
</v-tab>
</v-tabs>
<BaseButton
v-if="route.name === TABS.view"
color="info"
:icon="$globals.icons.cartCheck"
:text="$t('meal-plan.add-all-to-list')"
:disabled="!hasRecipes"
:loading="state.addAllLoading"
class="ml-auto mr-4"
@click="addAllToList"
/>
<ButtonLink
:icon="$globals.icons.calendar"
:to="`/household/mealplan/settings`"
@@ -72,15 +88,27 @@
<script lang="ts">
import { isSameDay, addDays, parseISO, format, isValid } from "date-fns";
import RecipeDialogAddToShoppingList from "~/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue";
import { useHouseholdSelf } from "~/composables/use-households";
import { useMealplans } from "~/composables/use-group-mealplan";
import { useUserMealPlanPreferences } from "~/composables/use-users/preferences";
import type { ShoppingListSummary } from "~/lib/api/types/household";
import { useUserApi } from "~/composables/api";
export default defineNuxtComponent({
components: {
RecipeDialogAddToShoppingList,
},
setup() {
const TABS = {
view: "household-mealplan-planner-view",
edit: "household-mealplan-planner-edit",
};
const route = useRoute();
const router = useRouter();
const i18n = useI18n();
const api = useUserApi();
const { household } = useHouseholdSelf();
useSeoMeta({
@@ -96,7 +124,7 @@ export default defineNuxtComponent({
// Force to /view if current route is /planner
if (route.path === "/household/mealplan/planner") {
router.push({
name: "household-mealplan-planner-view",
name: TABS.view,
query: route.query,
});
}
@@ -120,8 +148,12 @@ export default defineNuxtComponent({
start: initialStartDate,
picker: false,
end: initialEndDate,
shoppingListDialog: false,
addAllLoading: false,
});
const shoppingLists = ref<ShoppingListSummary[]>();
const firstDayOfWeek = computed(() => {
return household.value?.preferences?.firstDayOfWeek || 0;
});
@@ -145,7 +177,7 @@ export default defineNuxtComponent({
watch(weekRange, (newRange) => {
// Keep current route name and params, just update the query
router.replace({
name: route.name || "household-mealplan-planner-view",
name: route.name || TABS.view,
params: route.params,
query: {
...route.query,
@@ -193,7 +225,41 @@ export default defineNuxtComponent({
});
});
const hasRecipes = computed(() => {
return mealsByDate.value.some(day => day.meals.some(meal => meal.recipe));
});
const weekRecipesWithScales = computed(() => {
const allRecipes: any[] = [];
for (const day of mealsByDate.value) {
for (const meal of day.meals) {
if (meal.recipe) {
allRecipes.push(meal.recipe);
}
}
}
return allRecipes.map(recipe => ({
scale: 1,
...recipe,
}));
});
async function getShoppingLists() {
const { data } = await api.shopping.lists.getAll(1, -1, { orderBy: "name", orderDirection: "asc" });
if (data) {
shoppingLists.value = data.items as ShoppingListSummary[] ?? [];
}
}
async function addAllToList() {
state.value.addAllLoading = true;
await getShoppingLists();
state.value.shoppingListDialog = true;
state.value.addAllLoading = false;
}
return {
TABS,
route,
state,
actions,
@@ -201,6 +267,10 @@ export default defineNuxtComponent({
weekRange,
firstDayOfWeek,
numberOfDays,
hasRecipes,
shoppingLists,
weekRecipesWithScales,
addAllToList,
};
},
});