diff --git a/frontend/components/Domain/Cookbook/CookbookPage.vue b/frontend/components/Domain/Cookbook/CookbookPage.vue index 44486e70d..5d771ad52 100644 --- a/frontend/components/Domain/Cookbook/CookbookPage.vue +++ b/frontend/components/Domain/Cookbook/CookbookPage.vue @@ -7,7 +7,7 @@ width="100%" max-width="1100px" :icon="$globals.icons.pages" - :title="$t('general.edit')" + :title="$tc('general.edit')" :submit-icon="$globals.icons.save" :submit-text="$tc('general.save')" :submit-disabled="!editTarget.queryFilterString" @@ -25,7 +25,7 @@ {{ book.name }} { + if (!($auth.user && book.value?.householdId)) { + return false; + } + + return $auth.user.householdId === book.value.householdId; + }) + const canEdit = computed(() => isOwnGroup.value && isOwnHousehold.value); + const dialogStates = reactive({ edit: false, }); @@ -118,7 +127,7 @@ recipes, removeRecipe, replaceRecipes, - isOwnGroup, + canEdit, dialogStates, editTarget, handleEditCookbook, diff --git a/frontend/components/Layout/DefaultLayout.vue b/frontend/components/Layout/DefaultLayout.vue index 062a70ed3..5e1ea6eca 100644 --- a/frontend/components/Layout/DefaultLayout.vue +++ b/frontend/components/Layout/DefaultLayout.vue @@ -82,12 +82,17 @@ import { computed, defineComponent, onMounted, ref, useContext, useRoute } from import { useLoggedInState } from "~/composables/use-logged-in-state"; import AppHeader from "@/components/Layout/LayoutParts/AppHeader.vue"; import AppSidebar from "@/components/Layout/LayoutParts/AppSidebar.vue"; -import { SidebarLinks } from "~/types/application-types"; +import { SideBarLink } from "~/types/application-types"; import LanguageDialog from "~/components/global/LanguageDialog.vue"; import TheSnackbar from "@/components/Layout/LayoutParts/TheSnackbar.vue"; import { useAppInfo } from "~/composables/api"; import { useCookbooks, usePublicCookbooks } from "~/composables/use-group-cookbooks"; +import { useCookbookPreferences } from "~/composables/use-users/preferences"; +import { useHouseholdStore, usePublicHouseholdStore } from "~/composables/store/use-household-store"; import { useToggleDarkMode } from "~/composables/use-utils"; +import { ReadCookBook } from "~/lib/api/types/cookbook"; +import { HouseholdSummary } from "~/lib/api/types/household"; + export default defineComponent({ components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar }, @@ -99,6 +104,15 @@ export default defineComponent({ const route = useRoute(); const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || ""); const { cookbooks } = isOwnGroup.value ? useCookbooks() : usePublicCookbooks(groupSlug.value || ""); + const cookbookPreferences = useCookbookPreferences(); + const { store: households } = isOwnGroup.value ? useHouseholdStore() : usePublicHouseholdStore(groupSlug.value || ""); + + const householdsById = computed(() => { + return households.value.reduce((acc, household) => { + acc[household.id] = household; + return acc; + }, {} as { [key: string]: HouseholdSummary }); + }); const appInfo = useAppInfo(); const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices); @@ -113,29 +127,57 @@ export default defineComponent({ sidebar.value = !$vuetify.breakpoint.md; }); - const cookbookLinks = computed(() => { - if (!cookbooks.value) return []; - return cookbooks.value.map((cookbook) => { - return { - key: cookbook.slug, - icon: $globals.icons.pages, - title: cookbook.name, - to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug as string}`, - }; - }); - }); - - interface Link { - insertDivider: boolean; - icon: string; - title: string; - subtitle: string | null; - to: string; - restricted: boolean; - hide: boolean; + function cookbookAsLink(cookbook: ReadCookBook): SideBarLink { + return { + key: cookbook.slug || "", + icon: $globals.icons.pages, + title: cookbook.name, + to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug || ""}`, + restricted: false, + }; } - const createLinks = computed(() => [ + const currentUserHouseholdId = computed(() => $auth.user?.householdId); + const cookbookLinks = computed(() => { + if (!cookbooks.value) { + return []; + } + cookbooks.value.sort((a, b) => (a.position || 0) - (b.position || 0)); + + const ownLinks: SideBarLink[] = []; + const links: SideBarLink[] = []; + const cookbooksByHousehold = cookbooks.value.reduce((acc, cookbook) => { + const householdName = householdsById.value[cookbook.householdId]?.name || ""; + if (!acc[householdName]) { + acc[householdName] = []; + } + acc[householdName].push(cookbook); + return acc; + }, {} as Record); + + Object.entries(cookbooksByHousehold).forEach(([householdName, cookbooks]) => { + if (cookbooks[0].householdId === currentUserHouseholdId.value) { + ownLinks.push(...cookbooks.map(cookbookAsLink)); + } else { + links.push({ + key: householdName, + icon: $globals.icons.book, + title: householdName, + children: cookbooks.map(cookbookAsLink), + restricted: false, + }); + } + }); + + links.sort((a, b) => a.title.localeCompare(b.title)); + if ($auth.user && cookbookPreferences.value.hideOtherHouseholds) { + return ownLinks; + } else { + return [...ownLinks, ...links]; + } + }); + + const createLinks = computed(() => [ { insertDivider: false, icon: $globals.icons.link, @@ -165,7 +207,7 @@ export default defineComponent({ }, ]); - const bottomLinks = computed(() => [ + const bottomLinks = computed(() => [ { icon: $globals.icons.cog, title: i18n.tc("general.settings"), @@ -174,7 +216,7 @@ export default defineComponent({ }, ]); - const topLinks = computed(() => [ + const topLinks = computed(() => [ { icon: $globals.icons.silverwareForkKnife, to: `/g/${groupSlug.value}`, diff --git a/frontend/components/Layout/LayoutParts/AppSidebar.vue b/frontend/components/Layout/LayoutParts/AppSidebar.vue index d6c5597f9..ab3e3fb83 100644 --- a/frontend/components/Layout/LayoutParts/AppSidebar.vue +++ b/frontend/components/Layout/LayoutParts/AppSidebar.vue @@ -135,7 +135,7 @@