1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-02 20:15:24 +02:00

feat: Filter Recipes By Household (and a ton of bug fixes) (#4207)

Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
Michael Genson 2024-09-22 09:59:20 -05:00 committed by GitHub
parent 2a6922a85c
commit 7c274de778
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 896 additions and 590 deletions

View file

@ -45,7 +45,7 @@
import { defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api";
import HouseholdPreferencesEditor from "~/components/Domain/Household/HouseholdPreferencesEditor.vue";
import { useGroups } from "~/composables/use-groups";
import { useUserApi } from "~/composables/api";
import { useAdminApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { validators } from "~/composables/use-validators";
import { HouseholdInDB } from "~/lib/api/types/household";
@ -68,14 +68,14 @@ export default defineComponent({
const refHouseholdEditForm = ref<VForm | null>(null);
const userApi = useUserApi();
const adminApi = useAdminApi();
const household = ref<HouseholdInDB | null>(null);
const userError = ref(false);
onMounted(async () => {
const { data, error } = await userApi.households.getOne(householdId);
const { data, error } = await adminApi.households.getOne(householdId);
if (error?.response?.status === 404) {
alert.error(i18n.tc("user.user-not-found"));
@ -92,7 +92,7 @@ export default defineComponent({
return;
}
const { response, data } = await userApi.households.updateOne(household.value.id, household.value);
const { response, data } = await adminApi.households.updateOne(household.value.id, household.value);
if (response?.status === 200 && data) {
household.value = data;
alert.success(i18n.tc("settings.settings-updated"));

View file

@ -88,7 +88,7 @@
import { defineComponent, reactive, toRefs, useContext, useRouter } from "@nuxtjs/composition-api";
import { fieldTypes } from "~/composables/forms";
import { useGroups } from "~/composables/use-groups";
import { useHouseholds } from "~/composables/use-households";
import { useAdminHouseholds } from "~/composables/use-households";
import { validators } from "~/composables/use-validators";
import { HouseholdInDB } from "~/lib/api/types/household";
@ -97,7 +97,7 @@ export default defineComponent({
setup() {
const { i18n } = useContext();
const { groups } = useGroups();
const { households, refreshAllHouseholds, deleteHousehold, createHousehold } = useHouseholds();
const { households, refreshAllHouseholds, deleteHousehold, createHousehold } = useAdminHouseholds();
const state = reactive({
createDialog: false,

View file

@ -80,7 +80,7 @@
import { computed, defineComponent, useRoute, onMounted, ref, useContext } from "@nuxtjs/composition-api";
import { useAdminApi, useUserApi } from "~/composables/api";
import { useGroups } from "~/composables/use-groups";
import { useHouseholds } from "~/composables/use-households";
import { useAdminHouseholds } from "~/composables/use-households";
import { alert } from "~/composables/use-toast";
import { useUserForm } from "~/composables/use-users";
import { validators } from "~/composables/use-validators";
@ -92,7 +92,7 @@ export default defineComponent({
setup() {
const { userForm } = useUserForm();
const { groups } = useGroups();
const { useHouseholdsInGroup } = useHouseholds();
const { useHouseholdsInGroup } = useAdminHouseholds();
const { i18n } = useContext();
const route = useRoute();

View file

@ -50,7 +50,7 @@
import { computed, defineComponent, useRouter, reactive, ref, toRefs, watch } from "@nuxtjs/composition-api";
import { useAdminApi } from "~/composables/api";
import { useGroups } from "~/composables/use-groups";
import { useHouseholds } from "~/composables/use-households";
import { useAdminHouseholds } from "~/composables/use-households";
import { useUserForm } from "~/composables/use-users";
import { validators } from "~/composables/use-validators";
import { VForm } from "~/types/vuetify";
@ -60,7 +60,7 @@ export default defineComponent({
setup() {
const { userForm } = useUserForm();
const { groups } = useGroups();
const { useHouseholdsInGroup } = useHouseholds();
const { useHouseholdsInGroup } = useAdminHouseholds();
const router = useRouter();
// ==============================================

View file

@ -94,7 +94,7 @@
<script lang="ts">
import { computed, defineComponent, ref, useContext, useRouter } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { useAdminApi, useUserApi } from "~/composables/api";
import { useLocales } from "~/composables/use-locales";
import { alert } from "~/composables/use-toast";
import { useUserRegistrationForm } from "~/composables/use-users/user-registration-form";
@ -108,7 +108,8 @@ export default defineComponent({
// ================================================================
// Setup
const { $auth, $globals, i18n } = useContext();
const api = useUserApi();
const userApi = useUserApi();
const adminApi = useAdminApi();
const groupSlug = computed(() => $auth.user?.groupSlug);
const { locale } = useLocales();
@ -264,7 +265,7 @@ export default defineComponent({
async function updateUser() {
// @ts-ignore-next-line user will never be null here
const { response } = await api.users.updateOne($auth.user?.id, {
const { response } = await userApi.users.updateOne($auth.user?.id, {
...$auth.user,
email: accountDetails.email.value,
username: accountDetails.username.value,
@ -285,7 +286,7 @@ export default defineComponent({
}
async function updatePassword() {
const { response } = await api.users.changePassword({
const { response } = await userApi.users.changePassword({
currentPassword: "MyPassword",
newPassword: credentials.password1.value,
});
@ -303,7 +304,7 @@ export default defineComponent({
async function updateGroup() {
// @ts-ignore-next-line user will never be null here
const { data } = await api.groups.getOne($auth.user?.groupId);
const { data } = await userApi.groups.getOne($auth.user?.groupId);
if (!data || !data.preferences) {
alert.error(i18n.tc("events.something-went-wrong"));
return;
@ -320,7 +321,7 @@ export default defineComponent({
}
// @ts-ignore-next-line user will never be null here
const { response } = await api.groups.updateOne($auth.user?.groupId, payload);
const { response } = await userApi.groups.updateOne($auth.user?.groupId, payload);
if (!response || response.status !== 200) {
alert.error(i18n.tc("events.something-went-wrong"));
}
@ -328,7 +329,7 @@ export default defineComponent({
async function updateHousehold() {
// @ts-ignore-next-line user will never be null here
const { data } = await api.households.getOne($auth.user?.householdId);
const { data } = await adminApi.households.getOne($auth.user?.householdId);
if (!data || !data.preferences) {
alert.error(i18n.tc("events.something-went-wrong"));
return;
@ -346,28 +347,28 @@ export default defineComponent({
}
// @ts-ignore-next-line user will never be null here
const { response } = await api.households.updateOne($auth.user?.householdId, payload);
const { response } = await adminApi.households.updateOne($auth.user?.householdId, payload);
if (!response || response.status !== 200) {
alert.error(i18n.tc("events.something-went-wrong"));
}
}
async function seedFoods() {
const { response } = await api.seeders.foods({ locale: locale.value })
const { response } = await userApi.seeders.foods({ locale: locale.value })
if (!response || response.status !== 200) {
alert.error(i18n.tc("events.something-went-wrong"));
}
}
async function seedUnits() {
const { response } = await api.seeders.units({ locale: locale.value })
const { response } = await userApi.seeders.units({ locale: locale.value })
if (!response || response.status !== 200) {
alert.error(i18n.tc("events.something-went-wrong"));
}
}
async function seedLabels() {
const { response } = await api.seeders.labels({ locale: locale.value })
const { response } = await userApi.seeders.labels({ locale: locale.value })
if (!response || response.status !== 200) {
alert.error(i18n.tc("events.something-went-wrong"));
}

View file

@ -272,12 +272,10 @@ export default defineComponent({
const errors = ref<Error[]>([]);
function checkForUnit(unit?: IngredientUnit | CreateIngredientUnit) {
// @ts-expect-error; we're just checking if there's an id on this unit and returning a boolean
return !!unit?.id;
}
function checkForFood(food?: IngredientFood | CreateIngredientFood) {
// @ts-expect-error; we're just checking if there's an id on this food and returning a boolean
return !!food?.id;
}

View file

@ -1,8 +1,8 @@
<template>
<v-container>
<RecipeOrganizerPage
v-if="items"
:items="items"
v-if="store"
:items="store"
:icon="$globals.icons.categories"
item-type="categories"
@delete="actions.deleteOne"
@ -24,10 +24,10 @@ export default defineComponent({
},
middleware: ["auth", "group-only"],
setup() {
const { items, actions } = useCategoryStore();
const { store, actions } = useCategoryStore();
return {
items,
store,
actions,
};
},

View file

@ -1,8 +1,8 @@
<template>
<v-container>
<RecipeOrganizerPage
v-if="items"
:items="items"
v-if="store"
:items="store"
:icon="$globals.icons.tags"
item-type="tags"
@delete="actions.deleteOne"
@ -24,10 +24,10 @@ export default defineComponent({
},
middleware: ["auth", "group-only"],
setup() {
const { items, actions } = useTagStore();
const { store, actions } = useTagStore();
return {
items,
store,
actions,
};
},

View file

@ -29,7 +29,7 @@ export default defineComponent({
return {
dialog,
tools: toolStore.items,
tools: toolStore.store,
actions: toolStore.actions,
};
},

View file

@ -81,6 +81,7 @@
:headers.sync="tableHeaders"
:data="categories || []"
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
initial-sort="name"
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@delete-selected="bulkDeleteEventHandler"
@ -198,7 +199,7 @@ export default defineComponent({
state,
tableConfig,
tableHeaders,
categories: categoryStore.items,
categories: categoryStore.store,
validators,
// create

View file

@ -241,6 +241,8 @@
{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'},
{icon: $globals.icons.tags, text: $tc('data-pages.labels.assign-label'), event: 'assign-selected'}
]"
initial-sort="createdAt"
initial-sort-desc
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@create-one="createEventHandler"
@ -264,6 +266,9 @@
{{ item.onHand ? $globals.icons.check : $globals.icons.close }}
</v-icon>
</template>
<template #item.createdAt="{ item }">
{{ formatDate(item.createdAt) }}
</template>
<template #button-bottom>
<BaseButton @click="seedDialog = true">
<template #icon> {{ $globals.icons.database }} </template>
@ -326,8 +331,21 @@ export default defineComponent({
value: "onHand",
show: true,
},
{
text: i18n.tc("general.date-added"),
value: "createdAt",
show: false,
}
];
function formatDate(date: string) {
try {
return i18n.d(Date.parse(date), "medium");
} catch {
return "";
}
}
const foodStore = useFoodStore();
// ===============================================================
@ -453,7 +471,7 @@ export default defineComponent({
// ============================================================
// Labels
const { labels: allLabels } = useLabelStore();
const { store: allLabels } = useLabelStore();
// ============================================================
// Seed
@ -501,16 +519,15 @@ export default defineComponent({
bulkAssignTarget.value = [];
bulkAssignLabelId.value = undefined;
foodStore.actions.refresh();
// reload page, because foodStore.actions.refresh() does not update the table, reactivity for this seems to be broken (again)
document.location.reload();
}
return {
tableConfig,
tableHeaders,
foods: foodStore.foods,
foods: foodStore.store,
allLabels,
validators,
formatDate,
// Create
createDialog,
domNewFoodForm,

View file

@ -115,6 +115,7 @@
:headers.sync="tableHeaders"
:data="labels || []"
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
initial-sort="name"
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@delete-selected="bulkDeleteEventHandler"
@ -271,7 +272,7 @@ export default defineComponent({
state,
tableConfig,
tableHeaders,
labels: labelStore.labels,
labels: labelStore.store,
validators,
// create

View file

@ -101,6 +101,7 @@
:headers.sync="tableHeaders"
:data="actions || []"
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
initial-sort="title"
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@delete-selected="bulkDeleteEventHandler"

View file

@ -81,6 +81,7 @@
:headers.sync="tableHeaders"
:data="tags || []"
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
initial-sort="name"
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@delete-selected="bulkDeleteEventHandler"
@ -199,7 +200,7 @@ export default defineComponent({
state,
tableConfig,
tableHeaders,
tags: tagStore.items,
tags: tagStore.store,
validators,
// create

View file

@ -83,6 +83,7 @@
:headers.sync="tableHeaders"
:data="tools || []"
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
initial-sort="name"
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@delete-selected="bulkDeleteEventHandler"
@ -209,7 +210,7 @@ export default defineComponent({
state,
tableConfig,
tableHeaders,
tools: toolStore.items,
tools: toolStore.store,
validators,
// create

View file

@ -9,11 +9,11 @@
</template>
</i18n>
<v-autocomplete v-model="fromUnit" return-object :items="units" item-text="id" :label="$t('data-pages.units.source-unit')">
<v-autocomplete v-model="fromUnit" return-object :items="store" item-text="id" :label="$t('data-pages.units.source-unit')">
<template #selection="{ item }"> {{ item.name }}</template>
<template #item="{ item }"> {{ item.name }} </template>
</v-autocomplete>
<v-autocomplete v-model="toUnit" return-object :items="units" item-text="id" :label="$t('data-pages.units.target-unit')">
<v-autocomplete v-model="toUnit" return-object :items="store" item-text="id" :label="$t('data-pages.units.target-unit')">
<template #selection="{ item }"> {{ item.name }}</template>
<template #item="{ item }"> {{ item.name }} </template>
</v-autocomplete>
@ -185,7 +185,7 @@
</template>
</v-autocomplete>
<v-alert v-if="units && units.length > 0" type="error" class="mb-0 text-body-2">
<v-alert v-if="store && store.length > 0" type="error" class="mb-0 text-body-2">
{{ $t("data-pages.foods.seed-dialog-warning") }}
</v-alert>
</v-card-text>
@ -196,8 +196,10 @@
<CrudTable
:table-config="tableConfig"
:headers.sync="tableHeaders"
:data="units || []"
:data="store"
:bulk-actions="[{icon: $globals.icons.delete, text: $tc('general.delete'), event: 'delete-selected'}]"
initial-sort="createdAt"
initial-sort-desc
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@create-one="createEventHandler"
@ -221,6 +223,9 @@
{{ item.fraction ? $globals.icons.check : $globals.icons.close }}
</v-icon>
</template>
<template #item.createdAt="{ item }">
{{ formatDate(item.createdAt) }}
</template>
<template #button-bottom>
<BaseButton @click="seedDialog = true">
<template #icon> {{ $globals.icons.database }} </template>
@ -292,9 +297,22 @@ export default defineComponent({
value: "fraction",
show: true,
},
{
text: i18n.tc("general.date-added"),
value: "createdAt",
show: false,
},
];
const { units, actions: unitActions } = useUnitStore();
function formatDate(date: string) {
try {
return i18n.d(Date.parse(date), "medium");
} catch {
return "";
}
}
const { store, actions: unitActions } = useUnitStore();
// ============================================================
// Create Units
@ -447,8 +465,9 @@ export default defineComponent({
return {
tableConfig,
tableHeaders,
units,
store,
validators,
formatDate,
// Create
createDialog,
domNewUnitForm,

View file

@ -602,9 +602,9 @@ export default defineComponent({
const localLabels = ref<ShoppingListMultiPurposeLabelOut[]>()
const { labels: allLabels } = useLabelStore();
const { units: allUnits } = useUnitStore();
const { foods: allFoods } = useFoodStore();
const { store: allLabels } = useLabelStore();
const { store: allUnits } = useUnitStore();
const { store: allFoods } = useFoodStore();
function getLabelColor(item: ShoppingListItemOut | null) {
return item?.label?.color;

View file

@ -5,34 +5,40 @@
:icon="$globals.icons.heart"
:title="$tc('user.user-favorites')"
:recipes="recipes"
:query="query"
@sortRecipes="assignSorted"
@replaceRecipes="replaceRecipes"
@appendRecipes="appendRecipes"
@delete="removeRecipe"
/>
</v-container>
</template>
<script lang="ts">
import { defineComponent, useAsync, useRoute } from "@nuxtjs/composition-api";
import { defineComponent, useRoute } from "@nuxtjs/composition-api";
import RecipeCardSection from "~/components/Domain/Recipe/RecipeCardSection.vue";
import { useLazyRecipes } from "~/composables/recipes";
import { useLoggedInState } from "~/composables/use-logged-in-state";
import { useUserApi } from "~/composables/api";
import { useAsyncKey } from "~/composables/use-utils";
export default defineComponent({
components: { RecipeCardSection },
middleware: "auth",
setup() {
const api = useUserApi();
const route = useRoute();
const { isOwnGroup } = useLoggedInState();
const userId = route.value.params.id;
const recipes = useAsync(async () => {
const { data } = await api.recipes.getAll(1, -1, { queryFilter: `favoritedBy.id = "${userId}"` });
return data?.items || null;
}, useAsyncKey());
const query = { queryFilter: `favoritedBy.id = "${userId}"` }
const { recipes, appendRecipes, assignSorted, removeRecipe, replaceRecipes } = useLazyRecipes();
return {
query,
recipes,
isOwnGroup,
appendRecipes,
assignSorted,
removeRecipe,
replaceRecipes,
};
},
head() {