-
-
+
+
+
+
+ {{ $tc("search.clear-selection") }}
+
+
+
-
-
-
-
-
- {{ item.name }}
-
-
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
{{ $tc('search.no-results') }}
@@ -65,6 +80,10 @@ export default defineComponent({
type: Boolean,
default: undefined,
},
+ radio: {
+ type: Boolean,
+ default: false,
+ },
},
setup(props, context) {
const state = reactive({
@@ -86,6 +105,13 @@ export default defineComponent({
},
});
+ const selectedRadio = computed({
+ get: () => (selected.value.length > 0 ? selected.value[0] : null),
+ set: (value) => {
+ context.emit("input", value ? [value] : []);
+ },
+ });
+
const filtered = computed(() => {
if (!state.search) {
return props.items;
@@ -94,11 +120,26 @@ export default defineComponent({
return props.items.filter((item) => item.name.toLowerCase().includes(state.search.toLowerCase()));
});
+ const handleRadioClick = (item: SelectableItem) => {
+ if (selectedRadio.value === item) {
+ selectedRadio.value = null;
+ }
+ };
+
+ function clearSelection() {
+ selected.value = [];
+ selectedRadio.value = null;
+ state.search = "";
+ }
+
return {
requireAllValue,
state,
selected,
+ selectedRadio,
filtered,
+ handleRadioClick,
+ clearSelection,
};
},
});
diff --git a/frontend/components/global/CrudTable.vue b/frontend/components/global/CrudTable.vue
index caf25a803a27..1c7bfe3c9c26 100644
--- a/frontend/components/global/CrudTable.vue
+++ b/frontend/components/global/CrudTable.vue
@@ -44,6 +44,8 @@
item-key="id"
:show-select="bulkActions.length > 0"
:headers="activeHeaders"
+ :sort-by="initialSort"
+ :sort-desc="initialSortDesc"
:items="data || []"
:items-per-page="15"
:search="search"
@@ -126,6 +128,14 @@ export default defineComponent({
type: Array as () => BulkAction[],
default: () => [],
},
+ initialSort: {
+ type: String,
+ default: "id",
+ },
+ initialSortDesc: {
+ type: Boolean,
+ default: false,
+ },
},
setup(props, context) {
// ===========================================================
diff --git a/frontend/composables/api/index.ts b/frontend/composables/api/index.ts
index 3f9056368b82..20d74981d772 100644
--- a/frontend/composables/api/index.ts
+++ b/frontend/composables/api/index.ts
@@ -1,3 +1,3 @@
export { useAppInfo } from "./use-app-info";
export { useStaticRoutes } from "./static-routes";
-export { useAdminApi, useUserApi } from "./api-client";
+export { useAdminApi, usePublicApi, usePublicExploreApi, useUserApi } from "./api-client";
diff --git a/frontend/composables/partials/types.ts b/frontend/composables/partials/types.ts
new file mode 100644
index 000000000000..1be933e3e774
--- /dev/null
+++ b/frontend/composables/partials/types.ts
@@ -0,0 +1,3 @@
+export type BoundT = {
+ id?: string | number | null;
+};
diff --git a/frontend/composables/partials/use-actions-factory.ts b/frontend/composables/partials/use-actions-factory.ts
index f0b829c78bae..b17b9ac6a3ba 100644
--- a/frontend/composables/partials/use-actions-factory.ts
+++ b/frontend/composables/partials/use-actions-factory.ts
@@ -1,18 +1,15 @@
import { Ref, useAsync } from "@nuxtjs/composition-api";
import { useAsyncKey } from "../use-utils";
+import { BoundT } from "./types";
import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
import { QueryValue } from "~/lib/api/base/route";
-type BoundT = {
- id?: string | number | null;
-};
-
-interface PublicStoreActions {
+interface ReadOnlyStoreActions {
getAll(page?: number, perPage?: number, params?: any): Ref;
refresh(): Promise;
}
-interface StoreActions extends PublicStoreActions {
+interface StoreActions extends ReadOnlyStoreActions {
createOne(createData: T): Promise;
updateOne(updateData: T): Promise;
deleteOne(id: string | number): Promise;
@@ -20,16 +17,16 @@ interface StoreActions extends PublicStoreActions {
/**
- * usePublicStoreActions is a factory function that returns a set of methods
+ * useReadOnlyActions is a factory function that returns a set of methods
* that can be reused to manage the state of a data store without using
* Vuex. This is primarily used for basic GET/GETALL operations that required
* a lot of refreshing hooks to be called on operations
*/
-export function usePublicStoreActions(
+export function useReadOnlyActions(
api: BaseCRUDAPIReadOnly,
allRef: Ref | null,
loading: Ref
-): PublicStoreActions {
+): ReadOnlyStoreActions {
function getAll(page = 1, perPage = -1, params = {} as Record) {
params.orderBy ??= "name";
params.orderDirection ??= "asc";
diff --git a/frontend/composables/partials/use-store-factory.ts b/frontend/composables/partials/use-store-factory.ts
new file mode 100644
index 000000000000..f4f80e145886
--- /dev/null
+++ b/frontend/composables/partials/use-store-factory.ts
@@ -0,0 +1,53 @@
+import { ref, reactive, Ref } from "@nuxtjs/composition-api";
+import { useReadOnlyActions, useStoreActions } from "./use-actions-factory";
+import { BoundT } from "./types";
+import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
+
+export const useData = function(defaultObject: T) {
+ const data = reactive({ ...defaultObject });
+ function reset() {
+ Object.assign(data, defaultObject);
+ };
+
+ return { data, reset };
+}
+
+export const useReadOnlyStore = function(
+ store: Ref,
+ loading: Ref,
+ api: BaseCRUDAPIReadOnly,
+) {
+ const actions = {
+ ...useReadOnlyActions(api, store, loading),
+ flushStore() {
+ store.value = [];
+ },
+ };
+
+ if (!loading.value && (!store.value || store.value.length === 0)) {
+ const result = actions.getAll();
+ store.value = result.value || [];
+ }
+
+ return { store, actions };
+}
+
+export const useStore = function(
+ store: Ref,
+ loading: Ref,
+ api: BaseCRUDAPI,
+) {
+ const actions = {
+ ...useStoreActions(api, store, loading),
+ flushStore() {
+ store = ref([]);
+ },
+ };
+
+ if (!loading.value && (!store.value || store.value.length === 0)) {
+ const result = actions.getAll();
+ store.value = result.value || [];
+ }
+
+ return { store, actions };
+}
diff --git a/frontend/composables/recipes/use-recipes.ts b/frontend/composables/recipes/use-recipes.ts
index 14a6fbcfd1b2..c51149a29a85 100644
--- a/frontend/composables/recipes/use-recipes.ts
+++ b/frontend/composables/recipes/use-recipes.ts
@@ -32,6 +32,7 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
searchSeed: query?._searchSeed, // unused, but pass it along for completeness of data
search: query?.search,
cookbook: query?.cookbook,
+ households: query?.households,
categories: query?.categories,
requireAllCategories: query?.requireAllCategories,
tags: query?.tags,
diff --git a/frontend/composables/store/index.ts b/frontend/composables/store/index.ts
index e00aba57e1a1..9dad0b7f161e 100644
--- a/frontend/composables/store/index.ts
+++ b/frontend/composables/store/index.ts
@@ -1,6 +1,7 @@
-export { useFoodStore, useFoodData } from "./use-food-store";
-export { useUnitStore, useUnitData } from "./use-unit-store";
+export { useCategoryStore, usePublicCategoryStore, useCategoryData } from "./use-category-store";
+export { useFoodStore, usePublicFoodStore, useFoodData } from "./use-food-store";
+export { useHouseholdStore, usePublicHouseholdStore } from "./use-household-store";
export { useLabelStore, useLabelData } from "./use-label-store";
-export { useToolStore, useToolData } from "./use-tool-store";
-export { useCategoryStore, useCategoryData } from "./use-category-store";
-export { useTagStore, useTagData } from "./use-tag-store";
+export { useTagStore, usePublicTagStore, useTagData } from "./use-tag-store";
+export { useToolStore, usePublicToolStore, useToolData } from "./use-tool-store";
+export { useUnitStore, useUnitData } from "./use-unit-store";
diff --git a/frontend/composables/store/use-category-store.ts b/frontend/composables/store/use-category-store.ts
index 4801bc9ab4b4..e64cd060a592 100644
--- a/frontend/composables/store/use-category-store.ts
+++ b/frontend/composables/store/use-category-store.ts
@@ -1,73 +1,26 @@
-import { reactive, ref, Ref } from "@nuxtjs/composition-api";
-import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory";
-import { usePublicExploreApi } from "../api/api-client";
-import { useUserApi } from "~/composables/api";
+import { ref, Ref } from "@nuxtjs/composition-api";
+import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
import { RecipeCategory } from "~/lib/api/types/recipe";
+import { usePublicExploreApi, useUserApi } from "~/composables/api";
-const categoryStore: Ref = ref([]);
-const publicStoreLoading = ref(false);
-const storeLoading = ref(false);
+const store: Ref = ref([]);
+const loading = ref(false);
+const publicLoading = ref(false);
-export function useCategoryData() {
- const data = reactive({
+export const useCategoryData = function () {
+ return useData({
id: "",
name: "",
- slug: undefined,
+ slug: "",
});
-
- function reset() {
- data.id = "";
- data.name = "";
- data.slug = undefined;
- }
-
- return {
- data,
- reset,
- };
}
-export function usePublicCategoryStore(groupSlug: string) {
- const api = usePublicExploreApi(groupSlug).explore;
- const loading = publicStoreLoading;
-
- const actions = {
- ...usePublicStoreActions(api.categories, categoryStore, loading),
- flushStore() {
- categoryStore.value = [];
- },
- };
-
- if (!loading.value && (!categoryStore.value || categoryStore.value?.length === 0)) {
- actions.getAll();
- }
-
- return {
- items: categoryStore,
- actions,
- loading,
- };
-}
-
-export function useCategoryStore() {
- // passing the group slug switches to using the public API
+export const useCategoryStore = function () {
const api = useUserApi();
- const loading = storeLoading;
-
- const actions = {
- ...useStoreActions(api.categories, categoryStore, loading),
- flushStore() {
- categoryStore.value = [];
- },
- };
-
- if (!loading.value && (!categoryStore.value || categoryStore.value?.length === 0)) {
- actions.getAll();
- }
-
- return {
- items: categoryStore,
- actions,
- loading,
- };
+ return useStore(store, loading, api.categories);
+}
+
+export const usePublicCategoryStore = function (groupSlug: string) {
+ const api = usePublicExploreApi(groupSlug).explore;
+ return useReadOnlyStore(store, publicLoading, api.categories);
}
diff --git a/frontend/composables/store/use-food-store.ts b/frontend/composables/store/use-food-store.ts
index 4b02210c382a..f377763fea31 100644
--- a/frontend/composables/store/use-food-store.ts
+++ b/frontend/composables/store/use-food-store.ts
@@ -1,73 +1,28 @@
-import { ref, reactive, Ref } from "@nuxtjs/composition-api";
-import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory";
-import { usePublicExploreApi } from "../api/api-client";
-import { useUserApi } from "~/composables/api";
+import { ref, Ref } from "@nuxtjs/composition-api";
+import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
import { IngredientFood } from "~/lib/api/types/recipe";
+import { usePublicExploreApi, useUserApi } from "~/composables/api";
-let foodStore: Ref = ref([]);
-const publicStoreLoading = ref(false);
-const storeLoading = ref(false);
+const store: Ref = ref([]);
+const loading = ref(false);
+const publicLoading = ref(false);
-/**
- * useFoodData returns a template reactive object
- * for managing the creation of foods. It also provides a
- * function to reset the data back to the initial state.
- */
export const useFoodData = function () {
- const data: IngredientFood = reactive({
+ return useData({
id: "",
name: "",
description: "",
labelId: undefined,
onHand: false,
});
-
- function reset() {
- data.id = "";
- data.name = "";
- data.description = "";
- data.labelId = undefined;
- data.onHand = false;
- }
-
- return {
- data,
- reset,
- };
-};
-
-export const usePublicFoodStore = function (groupSlug: string) {
- const api = usePublicExploreApi(groupSlug).explore;
- const loading = publicStoreLoading;
-
- const actions = {
- ...usePublicStoreActions(api.foods, foodStore, loading),
- flushStore() {
- foodStore = ref([]);
- },
- };
-
- if (!loading.value && (!foodStore.value || foodStore.value.length === 0)) {
- foodStore = actions.getAll();
- }
-
- return { foods: foodStore, actions };
-};
+}
export const useFoodStore = function () {
const api = useUserApi();
- const loading = storeLoading;
+ return useStore(store, loading, api.foods);
+}
- const actions = {
- ...useStoreActions(api.foods, foodStore, loading),
- flushStore() {
- foodStore.value = [];
- },
- };
-
- if (!loading.value && (!foodStore.value || foodStore.value.length === 0)) {
- foodStore = actions.getAll();
- }
-
- return { foods: foodStore, actions };
-};
+export const usePublicFoodStore = function (groupSlug: string) {
+ const api = usePublicExploreApi(groupSlug).explore;
+ return useReadOnlyStore(store, publicLoading, api.foods);
+}
diff --git a/frontend/composables/store/use-household-store.ts b/frontend/composables/store/use-household-store.ts
new file mode 100644
index 000000000000..0b7c8eef1023
--- /dev/null
+++ b/frontend/composables/store/use-household-store.ts
@@ -0,0 +1,18 @@
+import { ref, Ref } from "@nuxtjs/composition-api";
+import { useReadOnlyStore } from "../partials/use-store-factory";
+import { HouseholdSummary } from "~/lib/api/types/household";
+import { usePublicExploreApi, useUserApi } from "~/composables/api";
+
+const store: Ref = ref([]);
+const loading = ref(false);
+const publicLoading = ref(false);
+
+export const useHouseholdStore = function () {
+ const api = useUserApi();
+ return useReadOnlyStore(store, loading, api.households);
+}
+
+export const usePublicHouseholdStore = function (groupSlug: string) {
+ const api = usePublicExploreApi(groupSlug).explore;
+ return useReadOnlyStore(store, publicLoading, api.households);
+}
diff --git a/frontend/composables/store/use-label-store.ts b/frontend/composables/store/use-label-store.ts
index 72654d3b63a8..0cd3bb58d9d5 100644
--- a/frontend/composables/store/use-label-store.ts
+++ b/frontend/composables/store/use-label-store.ts
@@ -1,50 +1,21 @@
-import { reactive, ref, Ref } from "@nuxtjs/composition-api";
-import { useStoreActions } from "../partials/use-actions-factory";
+import { ref, Ref } from "@nuxtjs/composition-api";
+import { useData, useStore } from "../partials/use-store-factory";
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
import { useUserApi } from "~/composables/api";
-let labelStore: Ref = ref([]);
-const storeLoading = ref(false);
+const store: Ref = ref([]);
+const loading = ref(false);
-export function useLabelData() {
- const data = reactive({
+export const useLabelData = function () {
+ return useData({
groupId: "",
id: "",
name: "",
color: "",
});
-
- function reset() {
- data.groupId = "";
- data.id = "";
- data.name = "";
- data.color = "";
- }
-
- return {
- data,
- reset,
- };
}
-export function useLabelStore() {
+export const useLabelStore = function () {
const api = useUserApi();
- const loading = storeLoading;
-
- const actions = {
- ...useStoreActions(api.multiPurposeLabels, labelStore, loading),
- flushStore() {
- labelStore.value = [];
- },
- };
-
- if (!loading.value && (!labelStore.value || labelStore.value?.length === 0)) {
- labelStore = actions.getAll();
- }
-
- return {
- labels: labelStore,
- actions,
- loading,
- };
+ return useStore(store, loading, api.multiPurposeLabels);
}
diff --git a/frontend/composables/store/use-tag-store.ts b/frontend/composables/store/use-tag-store.ts
index 395c8e487582..b5a30822aec6 100644
--- a/frontend/composables/store/use-tag-store.ts
+++ b/frontend/composables/store/use-tag-store.ts
@@ -1,72 +1,26 @@
-import { reactive, ref, Ref } from "@nuxtjs/composition-api";
-import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory";
-import { usePublicExploreApi } from "../api/api-client";
-import { useUserApi } from "~/composables/api";
+import { ref, Ref } from "@nuxtjs/composition-api";
+import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
import { RecipeTag } from "~/lib/api/types/recipe";
+import { usePublicExploreApi, useUserApi } from "~/composables/api";
-const items: Ref = ref([]);
-const publicStoreLoading = ref(false);
-const storeLoading = ref(false);
+const store: Ref = ref([]);
+const loading = ref(false);
+const publicLoading = ref(false);
-export function useTagData() {
- const data = reactive({
+export const useTagData = function () {
+ return useData({
id: "",
name: "",
- slug: undefined,
+ slug: "",
});
-
- function reset() {
- data.id = "";
- data.name = "";
- data.slug = undefined;
- }
-
- return {
- data,
- reset,
- };
}
-export function usePublicTagStore(groupSlug: string) {
- const api = usePublicExploreApi(groupSlug).explore;
- const loading = publicStoreLoading;
-
- const actions = {
- ...usePublicStoreActions(api.tags, items, loading),
- flushStore() {
- items.value = [];
- },
- };
-
- if (!loading.value && (!items.value || items.value?.length === 0)) {
- actions.getAll();
- }
-
- return {
- items,
- actions,
- loading,
- };
-}
-
-export function useTagStore() {
+export const useTagStore = function () {
const api = useUserApi();
- const loading = storeLoading;
-
- const actions = {
- ...useStoreActions(api.tags, items, loading),
- flushStore() {
- items.value = [];
- },
- };
-
- if (!loading.value && (!items.value || items.value?.length === 0)) {
- actions.getAll();
- }
-
- return {
- items,
- actions,
- loading,
- };
+ return useStore(store, loading, api.tags);
+}
+
+export const usePublicTagStore = function (groupSlug: string) {
+ const api = usePublicExploreApi(groupSlug).explore;
+ return useReadOnlyStore(store, publicLoading, api.tags);
}
diff --git a/frontend/composables/store/use-tool-store.ts b/frontend/composables/store/use-tool-store.ts
index 7b14381e5bb8..d27fa20c0897 100644
--- a/frontend/composables/store/use-tool-store.ts
+++ b/frontend/composables/store/use-tool-store.ts
@@ -1,74 +1,27 @@
-import { reactive, ref, Ref } from "@nuxtjs/composition-api";
-import { usePublicExploreApi } from "../api/api-client";
-import { usePublicStoreActions, useStoreActions } from "../partials/use-actions-factory";
-import { useUserApi } from "~/composables/api";
+import { ref, Ref } from "@nuxtjs/composition-api";
+import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
import { RecipeTool } from "~/lib/api/types/recipe";
+import { usePublicExploreApi, useUserApi } from "~/composables/api";
-const toolStore: Ref = ref([]);
-const publicStoreLoading = ref(false);
-const storeLoading = ref(false);
+const store: Ref = ref([]);
+const loading = ref(false);
+const publicLoading = ref(false);
-export function useToolData() {
- const data = reactive({
+export const useToolData = function () {
+ return useData({
id: "",
name: "",
- slug: undefined,
+ slug: "",
onHand: false,
});
-
- function reset() {
- data.id = "";
- data.name = "";
- data.slug = undefined;
- data.onHand = false;
- }
-
- return {
- data,
- reset,
- };
}
-export function usePublicToolStore(groupSlug: string) {
- const api = usePublicExploreApi(groupSlug).explore;
- const loading = publicStoreLoading;
-
- const actions = {
- ...usePublicStoreActions(api.tools, toolStore, loading),
- flushStore() {
- toolStore.value = [];
- },
- };
-
- if (!loading.value && (!toolStore.value || toolStore.value?.length === 0)) {
- actions.getAll();
- }
-
- return {
- items: toolStore,
- actions,
- loading,
- };
-}
-
-export function useToolStore() {
+export const useToolStore = function () {
const api = useUserApi();
- const loading = storeLoading;
-
- const actions = {
- ...useStoreActions(api.tools, toolStore, loading),
- flushStore() {
- toolStore.value = [];
- },
- };
-
- if (!loading.value && (!toolStore.value || toolStore.value?.length === 0)) {
- actions.getAll();
- }
-
- return {
- items: toolStore,
- actions,
- loading,
- };
+ return useStore(store, loading, api.tools);
+}
+
+export const usePublicToolStore = function (groupSlug: string) {
+ const api = usePublicExploreApi(groupSlug).explore;
+ return useReadOnlyStore(store, publicLoading, api.tools);
}
diff --git a/frontend/composables/store/use-unit-store.ts b/frontend/composables/store/use-unit-store.ts
index 527a2ea77079..3bf0926a66b4 100644
--- a/frontend/composables/store/use-unit-store.ts
+++ b/frontend/composables/store/use-unit-store.ts
@@ -1,53 +1,22 @@
-import { ref, reactive, Ref } from "@nuxtjs/composition-api";
-import { useStoreActions } from "../partials/use-actions-factory";
-import { useUserApi } from "~/composables/api";
+import { ref, Ref } from "@nuxtjs/composition-api";
+import { useData, useStore } from "../partials/use-store-factory";
import { IngredientUnit } from "~/lib/api/types/recipe";
+import { useUserApi } from "~/composables/api";
-let unitStore: Ref = ref([]);
-const storeLoading = ref(false);
+const store: Ref = ref([]);
+const loading = ref(false);
-/**
- * useUnitData returns a template reactive object
- * for managing the creation of units. It also provides a
- * function to reset the data back to the initial state.
- */
export const useUnitData = function () {
- const data: IngredientUnit = reactive({
+ return useData({
id: "",
name: "",
fraction: true,
abbreviation: "",
description: "",
});
-
- function reset() {
- data.id = "";
- data.name = "";
- data.fraction = true;
- data.abbreviation = "";
- data.description = "";
- }
-
- return {
- data,
- reset,
- };
-};
+}
export const useUnitStore = function () {
const api = useUserApi();
- const loading = storeLoading;
-
- const actions = {
- ...useStoreActions(api.units, unitStore, loading),
- flushStore() {
- unitStore.value = [];
- },
- };
-
- if (!loading.value && (!unitStore.value || unitStore.value.length === 0)) {
- unitStore = actions.getAll();
- }
-
- return { units: unitStore, actions };
-};
+ return useStore(store, loading, api.units);
+}
diff --git a/frontend/composables/use-households.ts b/frontend/composables/use-households.ts
index 44f9caf8f4ec..c22d04422def 100644
--- a/frontend/composables/use-households.ts
+++ b/frontend/composables/use-households.ts
@@ -1,5 +1,5 @@
import { computed, ref, Ref, useAsync } from "@nuxtjs/composition-api";
-import { useUserApi } from "~/composables/api";
+import { useAdminApi, useUserApi } from "~/composables/api";
import { HouseholdCreate, HouseholdInDB } from "~/lib/api/types/household";
const householdSelfRef = ref(null);
@@ -46,8 +46,8 @@ export const useHouseholdSelf = function () {
return { actions, household };
};
-export const useHouseholds = function () {
- const api = useUserApi();
+export const useAdminHouseholds = function () {
+ const api = useAdminApi();
const loading = ref(false);
function getAllHouseholds() {
diff --git a/frontend/composables/use-users/user-ratings.ts b/frontend/composables/use-users/user-ratings.ts
index cf29576786b6..0f82cd718939 100644
--- a/frontend/composables/use-users/user-ratings.ts
+++ b/frontend/composables/use-users/user-ratings.ts
@@ -7,34 +7,37 @@ const loading = ref(false);
const ready = ref(false);
export const useUserSelfRatings = function () {
- const { $auth } = useContext();
- const api = useUserApi();
+ const { $auth } = useContext();
+ const api = useUserApi();
- async function refreshUserRatings() {
- if (loading.value) {
- return;
- }
-
- loading.value = true;
- const { data } = await api.users.getSelfRatings();
- userRatings.value = data?.ratings || [];
- loading.value = false;
- ready.value = true;
+ async function refreshUserRatings() {
+ if (!$auth.user || loading.value) {
+ return;
}
- async function setRating(slug: string, rating: number | null, isFavorite: boolean | null) {
- loading.value = true;
- const userId = $auth.user?.id || "";
- await api.users.setRating(userId, slug, rating, isFavorite);
- loading.value = false;
- await refreshUserRatings();
- }
+ loading.value = true;
+ const { data } = await api.users.getSelfRatings();
+ userRatings.value = data?.ratings || [];
+ loading.value = false;
+ ready.value = true;
+ }
+ async function setRating(slug: string, rating: number | null, isFavorite: boolean | null) {
+ loading.value = true;
+ const userId = $auth.user?.id || "";
+ await api.users.setRating(userId, slug, rating, isFavorite);
+ loading.value = false;
+ await refreshUserRatings();
+ }
+
+ if (!ready.value) {
refreshUserRatings();
- return {
- userRatings,
- refreshUserRatings,
- setRating,
- ready,
- }
+ }
+
+ return {
+ userRatings,
+ refreshUserRatings,
+ setRating,
+ ready,
+ }
}
diff --git a/frontend/lang/messages/en-US.json b/frontend/lang/messages/en-US.json
index 382aa53aca0a..2dd9dcee3c2d 100644
--- a/frontend/lang/messages/en-US.json
+++ b/frontend/lang/messages/en-US.json
@@ -652,6 +652,7 @@
"or": "Or",
"has-any": "Has Any",
"has-all": "Has All",
+ "clear-selection": "Clear Selection",
"results": "Results",
"search": "Search",
"search-mealie": "Search Mealie (press /)",
diff --git a/frontend/lib/api/admin/admin-households.ts b/frontend/lib/api/admin/admin-households.ts
new file mode 100644
index 000000000000..1e49723b7603
--- /dev/null
+++ b/frontend/lib/api/admin/admin-households.ts
@@ -0,0 +1,13 @@
+import { BaseCRUDAPI } from "../base/base-clients";
+import { HouseholdCreate, HouseholdInDB, UpdateHouseholdAdmin } from "~/lib/api/types/household";
+const prefix = "/api";
+
+const routes = {
+ adminHouseholds: `${prefix}/admin/households`,
+ adminHouseholdsId: (id: string) => `${prefix}/admin/households/${id}`,
+};
+
+export class AdminHouseholdsApi extends BaseCRUDAPI {
+ baseRoute: string = routes.adminHouseholds;
+ itemRoute = routes.adminHouseholdsId;
+}
diff --git a/frontend/lib/api/client-admin.ts b/frontend/lib/api/client-admin.ts
index a0bbca8f80fe..bf151d390edf 100644
--- a/frontend/lib/api/client-admin.ts
+++ b/frontend/lib/api/client-admin.ts
@@ -1,5 +1,6 @@
import { AdminAboutAPI } from "./admin/admin-about";
import { AdminUsersApi } from "./admin/admin-users";
+import { AdminHouseholdsApi } from "./admin/admin-households";
import { AdminGroupsApi } from "./admin/admin-groups";
import { AdminBackupsApi } from "./admin/admin-backups";
import { AdminMaintenanceApi } from "./admin/admin-maintenance";
@@ -9,6 +10,7 @@ import { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class AdminAPI {
public about: AdminAboutAPI;
public users: AdminUsersApi;
+ public households: AdminHouseholdsApi;
public groups: AdminGroupsApi;
public backups: AdminBackupsApi;
public maintenance: AdminMaintenanceApi;
@@ -17,6 +19,7 @@ export class AdminAPI {
constructor(requests: ApiRequestInstance) {
this.about = new AdminAboutAPI(requests);
this.users = new AdminUsersApi(requests);
+ this.households = new AdminHouseholdsApi(requests);
this.groups = new AdminGroupsApi(requests);
this.backups = new AdminBackupsApi(requests);
this.maintenance = new AdminMaintenanceApi(requests);
diff --git a/frontend/lib/api/public/explore.ts b/frontend/lib/api/public/explore.ts
index 51f7d71357e5..7a26a6380b4e 100644
--- a/frontend/lib/api/public/explore.ts
+++ b/frontend/lib/api/public/explore.ts
@@ -4,6 +4,7 @@ import { PublicRecipeApi } from "./explore/recipes";
import { PublicFoodsApi } from "./explore/foods";
import { PublicCategoriesApi, PublicTagsApi, PublicToolsApi } from "./explore/organizers";
import { PublicCookbooksApi } from "./explore/cookbooks";
+import { PublicHouseholdApi } from "./explore/households";
export class ExploreApi extends BaseAPI {
public recipes: PublicRecipeApi;
@@ -12,6 +13,7 @@ export class ExploreApi extends BaseAPI {
public categories: PublicCategoriesApi;
public tags: PublicTagsApi;
public tools: PublicToolsApi;
+ public households: PublicHouseholdApi
constructor(requests: ApiRequestInstance, groupSlug: string) {
super(requests);
@@ -21,5 +23,6 @@ export class ExploreApi extends BaseAPI {
this.categories = new PublicCategoriesApi(requests, groupSlug);
this.tags = new PublicTagsApi(requests, groupSlug);
this.tools = new PublicToolsApi(requests, groupSlug);
+ this.households = new PublicHouseholdApi(requests, groupSlug);
}
}
diff --git a/frontend/lib/api/public/explore/households.ts b/frontend/lib/api/public/explore/households.ts
new file mode 100644
index 000000000000..188d75053c46
--- /dev/null
+++ b/frontend/lib/api/public/explore/households.ts
@@ -0,0 +1,20 @@
+import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
+import { HouseholdSummary } from "~/lib/api/types/household";
+import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
+
+const prefix = "/api";
+const exploreGroupSlug = (groupSlug: string | number) => `${prefix}/explore/groups/${groupSlug}`
+
+const routes = {
+ householdsGroupSlug: (groupSlug: string | number) => `${exploreGroupSlug(groupSlug)}/households`,
+ householdsGroupSlugHouseholdSlug: (groupSlug: string | number, householdSlug: string | number) => `${exploreGroupSlug(groupSlug)}/households/${householdSlug}`,
+};
+
+export class PublicHouseholdApi extends BaseCRUDAPIReadOnly {
+ baseRoute = routes.householdsGroupSlug(this.groupSlug);
+ itemRoute = (itemId: string | number) => routes.householdsGroupSlugHouseholdSlug(this.groupSlug, itemId);
+
+ constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
+ super(requests);
+ }
+}
diff --git a/frontend/lib/api/user/groups.ts b/frontend/lib/api/user/groups.ts
index 5dc85110abbe..fcd0a70d093d 100644
--- a/frontend/lib/api/user/groups.ts
+++ b/frontend/lib/api/user/groups.ts
@@ -1,6 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user";
-import { HouseholdSummary } from "~/lib/api/types/household";
import {
GroupAdminUpdate,
GroupStorage,
@@ -15,8 +14,6 @@ const routes = {
groupsSelf: `${prefix}/groups/self`,
preferences: `${prefix}/groups/preferences`,
storage: `${prefix}/groups/storage`,
- households: `${prefix}/groups/households`,
- householdsId: (id: string | number) => `${prefix}/groups/households/${id}`,
membersHouseholdId: (householdId: string | number | null) => {
return householdId ?
`${prefix}/households/members?householdId=${householdId}` :
@@ -47,14 +44,6 @@ export class GroupAPI extends BaseCRUDAPI(routes.membersHouseholdId(householdId));
}
- async fetchHouseholds() {
- return await this.requests.get(routes.households);
- }
-
- async fetchHousehold(householdId: string | number) {
- return await this.requests.get(routes.householdsId(householdId));
- }
-
async storage() {
return await this.requests.get(routes.storage);
}
diff --git a/frontend/lib/api/user/households.ts b/frontend/lib/api/user/households.ts
index b1909e78b08f..22fcad7d07f8 100644
--- a/frontend/lib/api/user/households.ts
+++ b/frontend/lib/api/user/households.ts
@@ -1,21 +1,20 @@
-import { BaseCRUDAPI } from "../base/base-clients";
+import { BaseCRUDAPIReadOnly } from "../base/base-clients";
import { UserOut } from "~/lib/api/types/user";
import {
- HouseholdCreate,
HouseholdInDB,
- UpdateHouseholdAdmin,
HouseholdStatistics,
ReadHouseholdPreferences,
SetPermissions,
UpdateHouseholdPreferences,
CreateInviteToken,
ReadInviteToken,
+ HouseholdSummary,
} from "~/lib/api/types/household";
const prefix = "/api";
const routes = {
- households: `${prefix}/admin/households`,
+ households: `${prefix}/groups/households`,
householdsSelf: `${prefix}/households/self`,
members: `${prefix}/households/members`,
permissions: `${prefix}/households/permissions`,
@@ -24,13 +23,13 @@ const routes = {
statistics: `${prefix}/households/statistics`,
invitation: `${prefix}/households/invitations`,
- householdsId: (id: string | number) => `${prefix}/admin/households/${id}`,
+ householdsId: (id: string | number) => `${prefix}/groups/households/${id}`,
};
-export class HouseholdAPI extends BaseCRUDAPI {
+export class HouseholdAPI extends BaseCRUDAPIReadOnly {
baseRoute = routes.households;
itemRoute = routes.householdsId;
- /** Returns the Group Data for the Current User
+ /** Returns the Household Data for the Current User
*/
async getCurrentUserHousehold() {
return await this.requests.get(routes.householdsSelf);
diff --git a/frontend/lib/api/user/recipes/recipe.ts b/frontend/lib/api/user/recipes/recipe.ts
index 8aaeb3172959..8e3b673b6dee 100644
--- a/frontend/lib/api/user/recipes/recipe.ts
+++ b/frontend/lib/api/user/recipes/recipe.ts
@@ -56,13 +56,14 @@ const routes = {
};
export type RecipeSearchQuery = {
- search: string;
+ search?: string;
orderDirection?: "asc" | "desc";
groupId?: string;
queryFilter?: string;
cookbook?: string;
+ households?: string[];
categories?: string[];
requireAllCategories?: boolean;
diff --git a/frontend/pages/admin/manage/households/_id.vue b/frontend/pages/admin/manage/households/_id.vue
index 3e897cff2b1a..f1fac388e4f7 100644
--- a/frontend/pages/admin/manage/households/_id.vue
+++ b/frontend/pages/admin/manage/households/_id.vue
@@ -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(null);
- const userApi = useUserApi();
+ const adminApi = useAdminApi();
const household = ref(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"));
diff --git a/frontend/pages/admin/manage/households/index.vue b/frontend/pages/admin/manage/households/index.vue
index 02c36914c803..122ba20b1921 100644
--- a/frontend/pages/admin/manage/households/index.vue
+++ b/frontend/pages/admin/manage/households/index.vue
@@ -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,
diff --git a/frontend/pages/admin/manage/users/_id.vue b/frontend/pages/admin/manage/users/_id.vue
index 6982658a6689..eac8b5bf9a64 100644
--- a/frontend/pages/admin/manage/users/_id.vue
+++ b/frontend/pages/admin/manage/users/_id.vue
@@ -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();
diff --git a/frontend/pages/admin/manage/users/create.vue b/frontend/pages/admin/manage/users/create.vue
index 0d0e380e2394..2dc0cc19af17 100644
--- a/frontend/pages/admin/manage/users/create.vue
+++ b/frontend/pages/admin/manage/users/create.vue
@@ -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();
// ==============================================
diff --git a/frontend/pages/admin/setup.vue b/frontend/pages/admin/setup.vue
index b244dcdf13eb..ef8e25c2a108 100644
--- a/frontend/pages/admin/setup.vue
+++ b/frontend/pages/admin/setup.vue
@@ -94,7 +94,7 @@