mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-02 20:15:24 +02:00
feat: Move "on hand" and "last made" to household (#4616)
Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com>
This commit is contained in:
parent
e565b919df
commit
e9892aba89
53 changed files with 1618 additions and 400 deletions
|
@ -204,6 +204,10 @@ export default defineComponent({
|
|||
shoppingListShowAllToggled: false,
|
||||
});
|
||||
|
||||
const userHousehold = computed(() => {
|
||||
return $auth.user?.householdSlug || "";
|
||||
});
|
||||
|
||||
const shoppingListChoices = computed(() => {
|
||||
return props.shoppingLists.filter((list) => preferences.value.viewAllLists || list.userId === $auth.user?.id);
|
||||
});
|
||||
|
@ -248,8 +252,9 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const shoppingListIngredients: ShoppingListIngredient[] = recipe.recipeIngredient.map((ing) => {
|
||||
const householdsWithFood = (ing.food?.householdsWithIngredientFood || []);
|
||||
return {
|
||||
checked: !ing.food?.onHand,
|
||||
checked: !householdsWithFood.includes(userHousehold.value),
|
||||
ingredient: ing,
|
||||
disableAmount: recipe.settings?.disableAmount || false,
|
||||
}
|
||||
|
@ -276,7 +281,8 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// Store the on-hand ingredients for later
|
||||
if (ing.ingredient.food?.onHand) {
|
||||
const householdsWithFood = (ing.ingredient.food?.householdsWithIngredientFood || []);
|
||||
if (householdsWithFood.includes(userHousehold.value)) {
|
||||
onHandIngs.push(ing);
|
||||
return sections;
|
||||
}
|
||||
|
|
|
@ -96,7 +96,12 @@
|
|||
<v-icon left>
|
||||
{{ $globals.icons.calendar }}
|
||||
</v-icon>
|
||||
{{ $t('recipe.last-made-date', { date: value ? new Date(value).toLocaleDateString($i18n.locale) : $t("general.never") } ) }}
|
||||
<div v-if="lastMadeReady">
|
||||
{{ $t('recipe.last-made-date', { date: lastMade ? new Date(lastMade).toLocaleDateString($i18n.locale) : $t("general.never") } ) }}
|
||||
</div>
|
||||
<div v-else>
|
||||
<AppLoader tiny />
|
||||
</div>
|
||||
</v-chip>
|
||||
</div>
|
||||
<div class="d-flex justify-center flex-wrap mt-1">
|
||||
|
@ -110,7 +115,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||
import { computed, defineComponent, onMounted, reactive, ref, toRefs, useContext } from "@nuxtjs/composition-api";
|
||||
import { whenever } from "@vueuse/core";
|
||||
import { VForm } from "~/types/vuetify";
|
||||
import { useUserApi } from "~/composables/api";
|
||||
|
@ -119,10 +124,6 @@ import { Recipe, RecipeTimelineEventIn } from "~/lib/api/types/recipe";
|
|||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
recipe: {
|
||||
type: Object as () => Recipe,
|
||||
required: true,
|
||||
|
@ -146,6 +147,20 @@ export default defineComponent({
|
|||
const newTimelineEventImagePreviewUrl = ref<string>();
|
||||
const newTimelineEventTimestamp = ref<string>();
|
||||
|
||||
const lastMade = ref(props.recipe.lastMade);
|
||||
const lastMadeReady = ref(false);
|
||||
onMounted(async () => {
|
||||
if (!$auth.user?.householdSlug) {
|
||||
lastMade.value = props.recipe.lastMade;
|
||||
} else {
|
||||
const { data } = await userApi.households.getCurrentUserHouseholdRecipe(props.recipe.slug || "");
|
||||
lastMade.value = data?.lastMade;
|
||||
}
|
||||
|
||||
lastMadeReady.value = true;
|
||||
});
|
||||
|
||||
|
||||
whenever(
|
||||
() => madeThisDialog.value,
|
||||
() => {
|
||||
|
@ -195,11 +210,9 @@ export default defineComponent({
|
|||
const newEvent = eventResponse.data;
|
||||
|
||||
// we also update the recipe's last made value
|
||||
if (!props.value || newTimelineEvent.value.timestamp > props.value) {
|
||||
if (!lastMade.value || newTimelineEvent.value.timestamp > lastMade.value) {
|
||||
lastMade.value = newTimelineEvent.value.timestamp;
|
||||
await userApi.recipes.updateLastMade(props.recipe.slug, newTimelineEvent.value.timestamp);
|
||||
|
||||
// update recipe in parent so the user can see it
|
||||
context.emit("input", newTimelineEvent.value.timestamp);
|
||||
}
|
||||
|
||||
// update the image, if provided
|
||||
|
@ -234,6 +247,8 @@ export default defineComponent({
|
|||
newTimelineEventImage,
|
||||
newTimelineEventImagePreviewUrl,
|
||||
newTimelineEventTimestamp,
|
||||
lastMade,
|
||||
lastMadeReady,
|
||||
createTimelineEvent,
|
||||
clearImage,
|
||||
uploadImage,
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
<v-col cols="12" class="d-flex flex-wrap justify-center">
|
||||
<RecipeLastMade
|
||||
v-if="isOwnGroup"
|
||||
:value="recipe.lastMade"
|
||||
:recipe="recipe"
|
||||
:class="true ? undefined : 'force-bottom'"
|
||||
/>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<h2 class="mb-2 mt-4">{{ $t('tool.required-tools') }}</h2>
|
||||
<v-list-item v-for="(tool, index) in recipe.tools" :key="index" dense>
|
||||
<v-checkbox
|
||||
v-model="recipe.tools[index].onHand"
|
||||
v-model="recipeTools[index].onHand"
|
||||
hide-details
|
||||
class="pt-0 my-auto py-auto"
|
||||
color="secondary"
|
||||
|
@ -26,14 +26,18 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { computed, defineComponent } from "@nuxtjs/composition-api";
|
||||
import { useLoggedInState } from "~/composables/use-logged-in-state";
|
||||
import { usePageState, usePageUser } from "~/composables/recipe-page/shared-state";
|
||||
import { useToolStore } from "~/composables/store";
|
||||
import { NoUndefinedField } from "~/lib/api/types/non-generated";
|
||||
import { Recipe } from "~/lib/api/types/recipe";
|
||||
import { Recipe, RecipeTool } from "~/lib/api/types/recipe";
|
||||
import RecipeIngredients from "~/components/Domain/Recipe/RecipeIngredients.vue";
|
||||
|
||||
interface RecipeToolWithOnHand extends RecipeTool {
|
||||
onHand: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
RecipeIngredients,
|
||||
|
@ -59,9 +63,31 @@ export default defineComponent({
|
|||
const { user } = usePageUser();
|
||||
const { isEditMode } = usePageState(props.recipe.slug);
|
||||
|
||||
const recipeTools = computed(() => {
|
||||
if (!(user.householdSlug && toolStore)) {
|
||||
return props.recipe.tools.map((tool) => ({ ...tool, onHand: false }) as RecipeToolWithOnHand);
|
||||
} else {
|
||||
return props.recipe.tools.map((tool) => {
|
||||
const onHand = tool.householdsWithTool?.includes(user.householdSlug) || false;
|
||||
return { ...tool, onHand } as RecipeToolWithOnHand;
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
function updateTool(index: number) {
|
||||
if (user.id && toolStore) {
|
||||
toolStore.actions.updateOne(props.recipe.tools[index]);
|
||||
if (user.id && user.householdSlug && toolStore) {
|
||||
const tool = recipeTools.value[index];
|
||||
if (tool.onHand && !tool.householdsWithTool?.includes(user.householdSlug)) {
|
||||
if (!tool.householdsWithTool) {
|
||||
tool.householdsWithTool = [user.householdSlug];
|
||||
} else {
|
||||
tool.householdsWithTool.push(user.householdSlug);
|
||||
}
|
||||
} else if (!tool.onHand && tool.householdsWithTool?.includes(user.householdSlug)) {
|
||||
tool.householdsWithTool = tool.householdsWithTool.filter((household) => household !== user.householdSlug);
|
||||
}
|
||||
|
||||
toolStore.actions.updateOne(tool);
|
||||
} else {
|
||||
console.log("no user, skipping server update");
|
||||
}
|
||||
|
@ -69,6 +95,7 @@ export default defineComponent({
|
|||
|
||||
return {
|
||||
toolStore,
|
||||
recipeTools,
|
||||
isEditMode,
|
||||
updateTool,
|
||||
};
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
</v-icon>
|
||||
<div v-if="large" class="text-small">
|
||||
<slot>
|
||||
{{ small ? "" : waitingText }}
|
||||
{{ (small || tiny) ? "" : waitingText }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</v-progress-circular>
|
||||
<div v-if="!large" class="text-small">
|
||||
<slot>
|
||||
{{ small ? "" : waitingTextCalculated }}
|
||||
{{ (small || tiny) ? "" : waitingTextCalculated }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,6 +31,10 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
tiny: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
@ -50,6 +54,13 @@ export default defineComponent({
|
|||
},
|
||||
setup(props) {
|
||||
const size = computed(() => {
|
||||
if (props.tiny) {
|
||||
return {
|
||||
width: 2,
|
||||
icon: 0,
|
||||
size: 25,
|
||||
};
|
||||
}
|
||||
if (props.small) {
|
||||
return {
|
||||
width: 2,
|
||||
|
|
|
@ -9,7 +9,6 @@ export const useTools = function (eager = true) {
|
|||
id: "",
|
||||
name: "",
|
||||
slug: "",
|
||||
onHand: false,
|
||||
});
|
||||
|
||||
const api = useUserApi();
|
||||
|
|
|
@ -13,7 +13,6 @@ export const useFoodData = function () {
|
|||
name: "",
|
||||
description: "",
|
||||
labelId: undefined,
|
||||
onHand: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,21 @@ import { useData, useReadOnlyStore, useStore } from "../partials/use-store-facto
|
|||
import { RecipeTool } from "~/lib/api/types/recipe";
|
||||
import { usePublicExploreApi, useUserApi } from "~/composables/api";
|
||||
|
||||
interface RecipeToolWithOnHand extends RecipeTool {
|
||||
onHand: boolean;
|
||||
}
|
||||
|
||||
const store: Ref<RecipeTool[]> = ref([]);
|
||||
const loading = ref(false);
|
||||
const publicLoading = ref(false);
|
||||
|
||||
export const useToolData = function () {
|
||||
return useData<RecipeTool>({
|
||||
return useData<RecipeToolWithOnHand>({
|
||||
id: "",
|
||||
name: "",
|
||||
slug: "",
|
||||
onHand: false,
|
||||
householdsWithTool: [],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ export interface RecipeTool {
|
|||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface CustomPageImport {
|
||||
|
|
|
@ -97,7 +97,7 @@ export interface RecipeTool {
|
|||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface SaveCookBook {
|
||||
|
|
|
@ -208,6 +208,27 @@ export interface ReadWebhook {
|
|||
householdId: string;
|
||||
id: string;
|
||||
}
|
||||
export interface HouseholdRecipeBase {
|
||||
lastMade?: string | null;
|
||||
}
|
||||
export interface HouseholdRecipeCreate {
|
||||
lastMade?: string | null;
|
||||
householdId: string;
|
||||
recipeId: string;
|
||||
}
|
||||
export interface HouseholdRecipeOut {
|
||||
lastMade?: string | null;
|
||||
householdId: string;
|
||||
recipeId: string;
|
||||
id: string;
|
||||
}
|
||||
export interface HouseholdRecipeSummary {
|
||||
lastMade?: string | null;
|
||||
recipeId: string;
|
||||
}
|
||||
export interface HouseholdRecipeUpdate {
|
||||
lastMade?: string | null;
|
||||
}
|
||||
export interface HouseholdSave {
|
||||
groupId: string;
|
||||
name: string;
|
||||
|
@ -297,7 +318,6 @@ export interface IngredientUnit {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
fraction?: boolean;
|
||||
abbreviation?: string;
|
||||
pluralAbbreviation?: string | null;
|
||||
|
@ -318,7 +338,6 @@ export interface CreateIngredientUnit {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
fraction?: boolean;
|
||||
abbreviation?: string;
|
||||
pluralAbbreviation?: string | null;
|
||||
|
@ -338,9 +357,9 @@ export interface IngredientFood {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
labelId?: string | null;
|
||||
aliases?: IngredientFoodAlias[];
|
||||
householdsWithIngredientFood?: string[];
|
||||
label?: MultiPurposeLabelSummary | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
|
@ -363,9 +382,9 @@ export interface CreateIngredientFood {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
labelId?: string | null;
|
||||
aliases?: CreateIngredientFoodAlias[];
|
||||
householdsWithIngredientFood?: string[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface CreateIngredientFoodAlias {
|
||||
|
@ -592,7 +611,7 @@ export interface RecipeTool {
|
|||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface ShoppingListRemoveRecipeParams {
|
||||
|
|
|
@ -117,7 +117,7 @@ export interface RecipeTool {
|
|||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
[k: string]: unknown;
|
||||
}
|
||||
export interface SavePlanEntry {
|
||||
|
|
|
@ -64,9 +64,9 @@ export interface CreateIngredientFood {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
labelId?: string | null;
|
||||
aliases?: CreateIngredientFoodAlias[];
|
||||
householdsWithIngredientFood?: string[];
|
||||
}
|
||||
export interface CreateIngredientFoodAlias {
|
||||
name: string;
|
||||
|
@ -79,7 +79,6 @@ export interface CreateIngredientUnit {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
fraction?: boolean;
|
||||
abbreviation?: string;
|
||||
pluralAbbreviation?: string | null;
|
||||
|
@ -136,9 +135,9 @@ export interface IngredientFood {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
labelId?: string | null;
|
||||
aliases?: IngredientFoodAlias[];
|
||||
householdsWithIngredientFood?: string[];
|
||||
label?: MultiPurposeLabelSummary | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
|
@ -167,7 +166,6 @@ export interface IngredientUnit {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
fraction?: boolean;
|
||||
abbreviation?: string;
|
||||
pluralAbbreviation?: string | null;
|
||||
|
@ -262,7 +260,7 @@ export interface RecipeTool {
|
|||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
}
|
||||
export interface RecipeStep {
|
||||
id?: string | null;
|
||||
|
@ -447,24 +445,24 @@ export interface RecipeTimelineEventUpdate {
|
|||
}
|
||||
export interface RecipeToolCreate {
|
||||
name: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
}
|
||||
export interface RecipeToolOut {
|
||||
name: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
id: string;
|
||||
slug: string;
|
||||
}
|
||||
export interface RecipeToolResponse {
|
||||
name: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
id: string;
|
||||
slug: string;
|
||||
recipes?: RecipeSummary[];
|
||||
}
|
||||
export interface RecipeToolSave {
|
||||
name: string;
|
||||
onHand?: boolean;
|
||||
householdsWithTool?: string[];
|
||||
groupId: string;
|
||||
}
|
||||
export interface RecipeZipTokenResponse {
|
||||
|
@ -478,9 +476,9 @@ export interface SaveIngredientFood {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
labelId?: string | null;
|
||||
aliases?: CreateIngredientFoodAlias[];
|
||||
householdsWithIngredientFood?: string[];
|
||||
groupId: string;
|
||||
}
|
||||
export interface SaveIngredientUnit {
|
||||
|
@ -491,7 +489,6 @@ export interface SaveIngredientUnit {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
fraction?: boolean;
|
||||
abbreviation?: string;
|
||||
pluralAbbreviation?: string | null;
|
||||
|
@ -536,7 +533,6 @@ export interface UnitFoodBase {
|
|||
extras?: {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
onHand?: boolean;
|
||||
}
|
||||
export interface UpdateImageResponse {
|
||||
image: string;
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
CreateInviteToken,
|
||||
ReadInviteToken,
|
||||
HouseholdSummary,
|
||||
HouseholdRecipeSummary,
|
||||
} from "~/lib/api/types/household";
|
||||
|
||||
const prefix = "/api";
|
||||
|
@ -26,6 +27,7 @@ const routes = {
|
|||
invitation: `${prefix}/households/invitations`,
|
||||
|
||||
householdsId: (id: string | number) => `${prefix}/groups/households/${id}`,
|
||||
householdsSelfRecipesSlug: (recipeSlug: string) => `${prefix}/households/self/recipes/${recipeSlug}`,
|
||||
};
|
||||
|
||||
export class HouseholdAPI extends BaseCRUDAPIReadOnly<HouseholdSummary> {
|
||||
|
@ -37,6 +39,10 @@ export class HouseholdAPI extends BaseCRUDAPIReadOnly<HouseholdSummary> {
|
|||
return await this.requests.get<HouseholdInDB>(routes.householdsSelf);
|
||||
}
|
||||
|
||||
async getCurrentUserHouseholdRecipe(recipeSlug: string) {
|
||||
return await this.requests.get<HouseholdRecipeSummary>(routes.householdsSelfRecipesSlug(recipeSlug));
|
||||
}
|
||||
|
||||
async getPreferences() {
|
||||
return await this.requests.get<ReadHouseholdPreferences>(routes.preferences);
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
:icon="$globals.icons.potSteam"
|
||||
:items="tools"
|
||||
item-type="tools"
|
||||
@delete="actions.deleteOne"
|
||||
@update="actions.updateOne"
|
||||
@delete="deleteOne"
|
||||
@update="updateOne"
|
||||
>
|
||||
<template #title> {{ $t("tool.tools") }} </template>
|
||||
</RecipeOrganizerPage>
|
||||
|
@ -14,9 +14,14 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from "@nuxtjs/composition-api";
|
||||
import { computed, defineComponent, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import RecipeOrganizerPage from "~/components/Domain/Recipe/RecipeOrganizerPage.vue";
|
||||
import { useToolStore } from "~/composables/store";
|
||||
import { RecipeTool } from "~/lib/api/types/recipe";
|
||||
|
||||
interface RecipeToolWithOnHand extends RecipeTool {
|
||||
onHand: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -24,13 +29,42 @@ export default defineComponent({
|
|||
},
|
||||
middleware: ["auth", "group-only"],
|
||||
setup() {
|
||||
const { $auth } = useContext();
|
||||
const toolStore = useToolStore();
|
||||
const dialog = ref(false);
|
||||
|
||||
const userHousehold = computed(() => $auth.user?.householdSlug || "");
|
||||
const tools = computed(() => toolStore.store.value.map((tool) => (
|
||||
{
|
||||
...tool,
|
||||
onHand: tool.householdsWithTool?.includes(userHousehold.value) || false
|
||||
} as RecipeToolWithOnHand
|
||||
)));
|
||||
|
||||
async function deleteOne(id: string | number) {
|
||||
await toolStore.actions.deleteOne(id);
|
||||
}
|
||||
|
||||
async function updateOne(tool: RecipeToolWithOnHand) {
|
||||
if (userHousehold.value) {
|
||||
if (tool.onHand && !tool.householdsWithTool?.includes(userHousehold.value)) {
|
||||
if (!tool.householdsWithTool) {
|
||||
tool.householdsWithTool = [userHousehold.value];
|
||||
} else {
|
||||
tool.householdsWithTool.push(userHousehold.value);
|
||||
}
|
||||
} else if (!tool.onHand && tool.householdsWithTool?.includes(userHousehold.value)) {
|
||||
tool.householdsWithTool = tool.householdsWithTool.filter((household) => household !== userHousehold.value);
|
||||
}
|
||||
}
|
||||
await toolStore.actions.updateOne(tool);
|
||||
}
|
||||
|
||||
return {
|
||||
dialog,
|
||||
tools: toolStore.store,
|
||||
actions: toolStore.actions,
|
||||
tools,
|
||||
deleteOne,
|
||||
updateOne,
|
||||
};
|
||||
},
|
||||
head() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Merge Dialog -->
|
||||
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.foods" :title="$t('data-pages.foods.combine-food')" @confirm="mergeFoods">
|
||||
<BaseDialog v-model="mergeDialog" :icon="$globals.icons.foods" :title="$tc('data-pages.foods.combine-food')" @confirm="mergeFoods">
|
||||
<v-card-text>
|
||||
<div>
|
||||
{{ $t("data-pages.foods.merge-dialog-text") }}
|
||||
|
@ -58,7 +58,7 @@
|
|||
<BaseDialog
|
||||
v-model="createDialog"
|
||||
:icon="$globals.icons.foods"
|
||||
:title="$t('data-pages.foods.create-food')"
|
||||
:title="$tc('data-pages.foods.create-food')"
|
||||
:submit-icon="$globals.icons.save"
|
||||
:submit-text="$tc('general.save')"
|
||||
@submit="createFood"
|
||||
|
@ -111,7 +111,7 @@
|
|||
<BaseDialog
|
||||
v-model="editDialog"
|
||||
:icon="$globals.icons.foods"
|
||||
:title="$t('data-pages.foods.edit-food')"
|
||||
:title="$tc('data-pages.foods.edit-food')"
|
||||
:submit-icon="$globals.icons.save"
|
||||
:submit-text="$tc('general.save')"
|
||||
@submit="editSaveFood"
|
||||
|
@ -196,7 +196,7 @@
|
|||
</v-card-text>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Bulk Asign Labels Dialog -->
|
||||
<!-- Bulk Assign Labels Dialog -->
|
||||
<BaseDialog
|
||||
v-model="bulkAssignLabelDialog"
|
||||
:title="$tc('data-pages.labels.assign-label')"
|
||||
|
@ -292,11 +292,20 @@ import { useFoodStore, useLabelStore } from "~/composables/store";
|
|||
import { VForm } from "~/types/vuetify";
|
||||
import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
|
||||
|
||||
interface CreateIngredientFoodWithOnHand extends CreateIngredientFood {
|
||||
onHand: boolean;
|
||||
householdsWithIngredientFood: string[];
|
||||
}
|
||||
|
||||
interface IngredientFoodWithOnHand extends IngredientFood {
|
||||
onHand: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { MultiPurposeLabel, RecipeDataAliasManagerDialog },
|
||||
setup() {
|
||||
const userApi = useUserApi();
|
||||
const { i18n } = useContext();
|
||||
const { $auth, i18n } = useContext();
|
||||
const tableConfig = {
|
||||
hideColumns: true,
|
||||
canExport: true,
|
||||
|
@ -352,15 +361,22 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
const userHousehold = computed(() => $auth.user?.householdSlug || "");
|
||||
const foodStore = useFoodStore();
|
||||
const foods = computed(() => foodStore.store.value.map((food) => {
|
||||
const onHand = food.householdsWithIngredientFood?.includes(userHousehold.value) || false;
|
||||
return { ...food, onHand } as IngredientFoodWithOnHand;
|
||||
}));
|
||||
|
||||
// ===============================================================
|
||||
// Food Creator
|
||||
|
||||
const domNewFoodForm = ref<VForm>();
|
||||
const createDialog = ref(false);
|
||||
const createTarget = ref<CreateIngredientFood>({
|
||||
const createTarget = ref<CreateIngredientFoodWithOnHand>({
|
||||
name: "",
|
||||
onHand: false,
|
||||
householdsWithIngredientFood: [],
|
||||
});
|
||||
|
||||
function createEventHandler() {
|
||||
|
@ -372,6 +388,10 @@ export default defineComponent({
|
|||
return;
|
||||
}
|
||||
|
||||
if (createTarget.value.onHand) {
|
||||
createTarget.value.householdsWithIngredientFood = [userHousehold.value];
|
||||
}
|
||||
|
||||
// @ts-expect-error the createOne function erroneously expects an id because it uses the IngredientFood type
|
||||
await foodStore.actions.createOne(createTarget.value);
|
||||
createDialog.value = false;
|
||||
|
@ -379,6 +399,8 @@ export default defineComponent({
|
|||
domNewFoodForm.value?.reset();
|
||||
createTarget.value = {
|
||||
name: "",
|
||||
onHand: false,
|
||||
householdsWithIngredientFood: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -386,10 +408,11 @@ export default defineComponent({
|
|||
// Food Editor
|
||||
|
||||
const editDialog = ref(false);
|
||||
const editTarget = ref<IngredientFood | null>(null);
|
||||
const editTarget = ref<IngredientFoodWithOnHand | null>(null);
|
||||
|
||||
function editEventHandler(item: IngredientFood) {
|
||||
function editEventHandler(item: IngredientFoodWithOnHand) {
|
||||
editTarget.value = item;
|
||||
editTarget.value.onHand = item.householdsWithIngredientFood?.includes(userHousehold.value) || false;
|
||||
editDialog.value = true;
|
||||
}
|
||||
|
||||
|
@ -397,6 +420,17 @@ export default defineComponent({
|
|||
if (!editTarget.value) {
|
||||
return;
|
||||
}
|
||||
if (editTarget.value.onHand && !editTarget.value.householdsWithIngredientFood?.includes(userHousehold.value)) {
|
||||
if (!editTarget.value.householdsWithIngredientFood) {
|
||||
editTarget.value.householdsWithIngredientFood = [userHousehold.value];
|
||||
} else {
|
||||
editTarget.value.householdsWithIngredientFood.push(userHousehold.value);
|
||||
}
|
||||
} else if (!editTarget.value.onHand && editTarget.value.householdsWithIngredientFood?.includes(userHousehold.value)) {
|
||||
editTarget.value.householdsWithIngredientFood = editTarget.value.householdsWithIngredientFood.filter(
|
||||
(household) => household !== userHousehold.value
|
||||
);
|
||||
}
|
||||
|
||||
await foodStore.actions.updateOne(editTarget.value);
|
||||
editDialog.value = false;
|
||||
|
@ -406,8 +440,8 @@ export default defineComponent({
|
|||
// Food Delete
|
||||
|
||||
const deleteDialog = ref(false);
|
||||
const deleteTarget = ref<IngredientFood | null>(null);
|
||||
function deleteEventHandler(item: IngredientFood) {
|
||||
const deleteTarget = ref<IngredientFoodWithOnHand | null>(null);
|
||||
function deleteEventHandler(item: IngredientFoodWithOnHand) {
|
||||
deleteTarget.value = item;
|
||||
deleteDialog.value = true;
|
||||
}
|
||||
|
@ -421,9 +455,9 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
const bulkDeleteDialog = ref(false);
|
||||
const bulkDeleteTarget = ref<IngredientFood[]>([]);
|
||||
const bulkDeleteTarget = ref<IngredientFoodWithOnHand[]>([]);
|
||||
|
||||
function bulkDeleteEventHandler(selection: IngredientFood[]) {
|
||||
function bulkDeleteEventHandler(selection: IngredientFoodWithOnHand[]) {
|
||||
bulkDeleteTarget.value = selection;
|
||||
bulkDeleteDialog.value = true;
|
||||
}
|
||||
|
@ -455,8 +489,8 @@ export default defineComponent({
|
|||
// Merge Foods
|
||||
|
||||
const mergeDialog = ref(false);
|
||||
const fromFood = ref<IngredientFood | null>(null);
|
||||
const toFood = ref<IngredientFood | null>(null);
|
||||
const fromFood = ref<IngredientFoodWithOnHand | null>(null);
|
||||
const toFood = ref<IngredientFoodWithOnHand | null>(null);
|
||||
|
||||
const canMerge = computed(() => {
|
||||
return fromFood.value && toFood.value && fromFood.value.id !== toFood.value.id;
|
||||
|
@ -506,10 +540,10 @@ export default defineComponent({
|
|||
// ============================================================
|
||||
// Bulk Assign Labels
|
||||
const bulkAssignLabelDialog = ref(false);
|
||||
const bulkAssignTarget = ref<IngredientFood[]>([]);
|
||||
const bulkAssignTarget = ref<IngredientFoodWithOnHand[]>([]);
|
||||
const bulkAssignLabelId = ref<string | undefined>();
|
||||
|
||||
function bulkAssignEventHandler(selection: IngredientFood[]) {
|
||||
function bulkAssignEventHandler(selection: IngredientFoodWithOnHand[]) {
|
||||
bulkAssignTarget.value = selection;
|
||||
bulkAssignLabelDialog.value = true;
|
||||
}
|
||||
|
@ -530,7 +564,7 @@ export default defineComponent({
|
|||
return {
|
||||
tableConfig,
|
||||
tableHeaders,
|
||||
foods: foodStore.store,
|
||||
foods,
|
||||
allLabels,
|
||||
validators,
|
||||
formatDate,
|
||||
|
|
|
@ -109,11 +109,6 @@
|
|||
<template #button-row>
|
||||
<BaseButton create @click="state.createDialog = true">{{ $t("general.create") }}</BaseButton>
|
||||
</template>
|
||||
<template #item.onHand="{ item }">
|
||||
<v-icon :color="item.onHand ? 'success' : undefined">
|
||||
{{ item.onHand ? $globals.icons.check : $globals.icons.close }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</CrudTable>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -101,14 +101,18 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import { computed, defineComponent, reactive, ref, useContext } from "@nuxtjs/composition-api";
|
||||
import { validators } from "~/composables/use-validators";
|
||||
import { useToolStore, useToolData } from "~/composables/store";
|
||||
import { RecipeTool } from "~/lib/api/types/admin";
|
||||
import { RecipeTool } from "~/lib/api/types/recipe";
|
||||
|
||||
interface RecipeToolWithOnHand extends RecipeTool {
|
||||
onHand: boolean;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { i18n } = useContext();
|
||||
const { $auth, i18n } = useContext();
|
||||
const tableConfig = {
|
||||
hideColumns: true,
|
||||
canExport: true,
|
||||
|
@ -138,27 +142,38 @@ export default defineComponent({
|
|||
bulkDeleteDialog: false,
|
||||
});
|
||||
|
||||
const userHousehold = computed(() => $auth.user?.householdSlug || "");
|
||||
const toolData = useToolData();
|
||||
const toolStore = useToolStore();
|
||||
const tools = computed(() => toolStore.store.value.map((tools) => {
|
||||
const onHand = tools.householdsWithTool?.includes(userHousehold.value) || false;
|
||||
return { ...tools, onHand } as RecipeToolWithOnHand;
|
||||
}));
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Create Tag
|
||||
// Create Tool
|
||||
|
||||
async function createTool() {
|
||||
if (toolData.data.onHand) {
|
||||
toolData.data.householdsWithTool = [userHousehold.value];
|
||||
} else {
|
||||
toolData.data.householdsWithTool = [];
|
||||
}
|
||||
|
||||
// @ts-ignore - only property really required is the name and onHand (RecipeOrganizerPage)
|
||||
await toolStore.actions.createOne({ name: toolData.data.name, onHand: toolData.data.onHand });
|
||||
await toolStore.actions.createOne({ name: toolData.data.name, householdsWithTool: toolData.data.householdsWithTool });
|
||||
toolData.reset();
|
||||
state.createDialog = false;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Edit Tag
|
||||
// Edit Tool
|
||||
|
||||
const editTarget = ref<RecipeTool | null>(null);
|
||||
const editTarget = ref<RecipeToolWithOnHand | null>(null);
|
||||
|
||||
function editEventHandler(item: RecipeTool) {
|
||||
function editEventHandler(item: RecipeToolWithOnHand) {
|
||||
state.editDialog = true;
|
||||
editTarget.value = item;
|
||||
}
|
||||
|
@ -167,17 +182,29 @@ export default defineComponent({
|
|||
if (!editTarget.value) {
|
||||
return;
|
||||
}
|
||||
if (editTarget.value.onHand && !editTarget.value.householdsWithTool?.includes(userHousehold.value)) {
|
||||
if (!editTarget.value.householdsWithTool) {
|
||||
editTarget.value.householdsWithTool = [userHousehold.value];
|
||||
} else {
|
||||
editTarget.value.householdsWithTool.push(userHousehold.value);
|
||||
}
|
||||
} else if (!editTarget.value.onHand && editTarget.value.householdsWithTool?.includes(userHousehold.value)) {
|
||||
editTarget.value.householdsWithTool = editTarget.value.householdsWithTool.filter(
|
||||
(household) => household !== userHousehold.value
|
||||
);
|
||||
}
|
||||
|
||||
await toolStore.actions.updateOne(editTarget.value);
|
||||
state.editDialog = false;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Delete Tag
|
||||
// Delete Tool
|
||||
|
||||
const deleteTarget = ref<RecipeTool | null>(null);
|
||||
const deleteTarget = ref<RecipeToolWithOnHand | null>(null);
|
||||
|
||||
function deleteEventHandler(item: RecipeTool) {
|
||||
function deleteEventHandler(item: RecipeToolWithOnHand) {
|
||||
state.deleteDialog = true;
|
||||
deleteTarget.value = item;
|
||||
}
|
||||
|
@ -191,10 +218,10 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// ============================================================
|
||||
// Bulk Delete Tag
|
||||
// Bulk Delete Tool
|
||||
|
||||
const bulkDeleteTarget = ref<RecipeTool[]>([]);
|
||||
function bulkDeleteEventHandler(selection: RecipeTool[]) {
|
||||
const bulkDeleteTarget = ref<RecipeToolWithOnHand[]>([]);
|
||||
function bulkDeleteEventHandler(selection: RecipeToolWithOnHand[]) {
|
||||
bulkDeleteTarget.value = selection;
|
||||
state.bulkDeleteDialog = true;
|
||||
}
|
||||
|
@ -210,7 +237,7 @@ export default defineComponent({
|
|||
state,
|
||||
tableConfig,
|
||||
tableHeaders,
|
||||
tools: toolStore.store,
|
||||
tools,
|
||||
validators,
|
||||
|
||||
// create
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue