1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 13:35:23 +02:00

Feature/automated meal planner (#939)

* cleanup oversized buttons

* add get all by category function to reciep repos

* fix shopping-list can_merge logic

* use randomized data for testing

* add random getter to repository for meal-planner

* add stub route for random meals

* cleanup global namespace

* add rules database type

* fix type

* add plan rules schema

* test plan rules methods

* add mealplan rules controller

* add new repository

* update frontend types

* formatting

* fix regression

* update autogenerated types

* add api class for mealplan rules

* add tests and fix bugs

* fix data returns

* proof of concept rules editor

* add tag support

* remove old group categories

* add tag support

* implement random by rules api

* change snack to sides

* remove incorrect typing

* split repo for custom methods

* fix query and use and_ clause

* use repo function

* remove old test

* update changelog
This commit is contained in:
Hayden 2022-02-07 19:03:11 -09:00 committed by GitHub
parent 40d1f586cd
commit d1024e272d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1153 additions and 175 deletions

View file

@ -0,0 +1,14 @@
import { BaseCRUDAPI } from "../_base";
import { PlanRulesCreate, PlanRulesOut } from "~/types/api-types/meal-plan";
const prefix = "/api";
const routes = {
rule: `${prefix}/groups/mealplans/rules`,
ruleId: (id: string | number) => `${prefix}/groups/mealplans/rules/${id}`,
};
export class MealPlanRulesApi extends BaseCRUDAPI<PlanRulesOut, PlanRulesCreate> {
baseRoute = routes.rule;
itemRoute = routes.ruleId;
}

View file

@ -1,13 +1,15 @@
import { BaseCRUDAPI } from "../_base";
import { CreatRandomEntry } from "~/types/api-types/meal-plan";
const prefix = "/api";
const routes = {
mealplan: `${prefix}/groups/mealplans`,
random: `${prefix}/groups/mealplans/random`,
mealplanId: (id: string | number) => `${prefix}/groups/mealplans/${id}`,
};
type PlanEntryType = "breakfast" | "lunch" | "dinner" | "snack";
type PlanEntryType = "breakfast" | "lunch" | "dinner" | "side";
export interface CreateMealPlan {
date: string;
@ -29,4 +31,9 @@ export interface MealPlan extends UpdateMealPlan {
export class MealPlanAPI extends BaseCRUDAPI<MealPlan, CreateMealPlan> {
baseRoute = routes.mealplan;
itemRoute = routes.mealplanId;
async setRandom(payload: CreatRandomEntry) {
console.log(payload);
return await this.requests.post<MealPlan>(routes.random, payload);
}
}

View file

@ -23,6 +23,7 @@ import { GroupReportsApi } from "./class-interfaces/group-reports";
import { ShoppingApi } from "./class-interfaces/group-shopping-lists";
import { MultiPurposeLabelsApi } from "./class-interfaces/group-multiple-purpose-labels";
import { GroupEventNotifierApi } from "./class-interfaces/group-event-notifier";
import { MealPlanRulesApi } from "./class-interfaces/group-mealplan-rules";
import { ApiRequestInstance } from "~/types/api";
class Api {
@ -40,6 +41,7 @@ class Api {
public groupWebhooks: WebhooksAPI;
public register: RegisterAPI;
public mealplans: MealPlanAPI;
public mealplanRules: MealPlanRulesApi;
public email: EmailAPI;
public bulk: BulkActionsAPI;
public groupMigration: GroupMigrationApi;
@ -67,6 +69,7 @@ class Api {
this.groupWebhooks = new WebhooksAPI(requests);
this.register = new RegisterAPI(requests);
this.mealplans = new MealPlanAPI(requests);
this.mealplanRules = new MealPlanRulesApi(requests);
this.grouperServerTasks = new GroupServerTaskAPI(requests);
// Group

View file

@ -0,0 +1,112 @@
<template>
<div>
<div class="d-md-flex" style="gap: 10px">
<v-select v-model="inputDay" :items="MEAL_DAY_OPTIONS" label="Rule Day"></v-select>
<v-select v-model="inputEntryType" :items="MEAL_TYPE_OPTIONS" label="Meal Type"></v-select>
</div>
<RecipeCategoryTagSelector v-model="inputCategories" />
<RecipeCategoryTagSelector v-model="inputTags" :tag-selector="true" />
{{ inputDay === "unset" ? "This rule will apply to all days" : `This rule applies on ${inputDay}s` }}
{{ inputEntryType === "unset" ? "for all meal types" : ` and for ${inputEntryType} meal types` }}
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from "@nuxtjs/composition-api";
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
const MEAL_TYPE_OPTIONS = [
{ text: "Breakfast", value: "breakfast" },
{ text: "Lunch", value: "lunch" },
{ text: "Dinner", value: "dinner" },
{ text: "Side", value: "side" },
{ text: "Any", value: "unset" },
];
const MEAL_DAY_OPTIONS = [
{ text: "Monday", value: "monday" },
{ text: "Tuesday", value: "tuesday" },
{ text: "Wednesday", value: "wednesday" },
{ text: "Thursday", value: "thursday" },
{ text: "Friday", value: "friday" },
{ text: "Sunday", value: "saturday" },
{ text: "Sunday", value: "sunday" },
{ text: "Any", value: "unset" },
];
export default defineComponent({
components: {
RecipeCategoryTagSelector,
},
props: {
day: {
type: String,
default: "unset",
},
entryType: {
type: String,
default: "unset",
},
categories: {
type: Array,
default: () => [],
},
tags: {
type: Array,
default: () => [],
},
showHelp: {
type: Boolean,
default: false,
},
},
setup(props, context) {
const inputDay = computed({
get: () => {
return props.day;
},
set: (val) => {
context.emit("update:day", val);
},
});
const inputEntryType = computed({
get: () => {
return props.entryType;
},
set: (val) => {
context.emit("update:entry-type", val);
},
});
const inputCategories = computed({
get: () => {
return props.categories;
},
set: (val) => {
context.emit("update:categories", val);
},
});
const inputTags = computed({
get: () => {
return props.tags;
},
set: (val) => {
context.emit("update:tags", val);
},
});
return {
MEAL_TYPE_OPTIONS,
MEAL_DAY_OPTIONS,
inputDay,
inputEntryType,
inputCategories,
inputTags,
};
},
});
</script>

View file

@ -4,13 +4,13 @@ import { useAsyncKey } from "./use-utils";
import { useUserApi } from "~/composables/api";
import { CreateMealPlan, UpdateMealPlan } from "~/api/class-interfaces/group-mealplan";
export type MealType = "breakfast" | "lunch" | "dinner" | "snack";
export type MealType = "breakfast" | "lunch" | "dinner" | "side";
export const planTypeOptions = [
{ text: "Breakfast", value: "breakfast" },
{ text: "Lunch", value: "lunch" },
{ text: "Dinner", value: "dinner" },
{ text: "Snack", value: "snack" },
{ text: "Side", value: "side" },
];
export interface DateRange {

View file

@ -34,32 +34,6 @@ export const useGroupSelf = function () {
return { actions, group };
};
export const useGroupCategories = function () {
const api = useUserApi();
const actions = {
getAll() {
const units = useAsync(async () => {
const { data } = await api.groups.getCategories();
return data;
}, useAsyncKey());
return units;
},
async updateAll() {
if (!categories.value) {
return;
}
const { data } = await api.groups.setCategories(categories.value);
categories.value = data;
},
};
const categories = actions.getAll();
return { actions, categories };
};
export const useGroups = function () {
const api = useUserApi();
const loading = ref(false);

View file

@ -60,7 +60,6 @@
</v-app>
</template>
<script lang="ts">
import { computed, defineComponent, onMounted, ref, useContext } from "@nuxtjs/composition-api";
import { useDark } from "@vueuse/core";
@ -151,7 +150,7 @@ export default defineComponent({
{
icon: this.$globals.icons.calendarMultiselect,
title: this.$t("meal-plan.meal-planner"),
to: "/meal-plan/planner",
to: "/group/mealplan/planner",
restricted: true,
},
{

View file

@ -70,7 +70,10 @@
</v-btn>
</div>
</div>
<v-switch v-model="edit" label="Editor"></v-switch>
<div class="d-flex align-center justify-space-between">
<v-switch v-model="edit" label="Editor"></v-switch>
<ButtonLink :icon="$globals.icons.calendar" to="/group/mealplan/settings" text="Settings" />
</div>
<v-row class="">
<v-col
v-for="(plan, index) in mealsByDate"
@ -143,9 +146,6 @@
</v-list>
</v-menu>
<v-spacer></v-spacer>
<v-btn color="info" class="mr-2" small icon>
<v-icon>{{ $globals.icons.cartCheck }}</v-icon>
</v-btn>
<v-btn color="error" small icon @click="actions.deleteOne(mealplan.id)">
<v-icon>{{ $globals.icons.delete }}</v-icon>
</v-btn>
@ -154,20 +154,50 @@
</draggable>
<!-- Day Column Actions -->
<v-card outlined class="mt-auto">
<v-card-actions class="d-flex">
<div style="width: 50%">
<v-btn block text @click="randomMeal(plan.date)">
<v-icon large>{{ $globals.icons.diceMultiple }}</v-icon>
</v-btn>
</div>
<div style="width: 50%">
<v-btn block text @click="openDialog(plan.date)">
<v-icon large>{{ $globals.icons.createAlt }}</v-icon>
</v-btn>
</div>
</v-card-actions>
</v-card>
<div class="d-flex justify-end">
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.diceMultiple,
text: 'Random Meal',
event: 'random',
children: [
{
icon: $globals.icons.dice,
text: 'Breakfast',
event: 'randomBreakfast',
},
{
icon: $globals.icons.dice,
text: 'Lunch',
event: 'randomLunch',
},
],
},
{
icon: $globals.icons.potSteam,
text: 'Random Dinner',
event: 'randomDinner',
},
{
icon: $globals.icons.bolwMixOutline,
text: 'Random Side',
event: 'randomSide',
},
{
icon: $globals.icons.createAlt,
text: $t('general.new'),
event: 'create',
},
]"
@create="openDialog(plan.date)"
@randomBreakfast="randomMeal(plan.date, 'breakfast')"
@randomLunch="randomMeal(plan.date, 'lunch')"
@randomDinner="randomMeal(plan.date, 'dinner')"
@randomSide="randomMeal(plan.date, 'side')"
/>
</div>
</template>
<template v-else-if="plan.meals">
<RecipeCard
@ -211,6 +241,7 @@ import { useRecipes, allRecipes } from "~/composables/recipes";
import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue";
import RecipeCard from "~/components/Domain/Recipe/RecipeCard.vue";
import { PlanEntryType } from "~/types/api-types/meal-plan";
import { useUserApi } from "~/composables/api";
export default defineComponent({
components: {
@ -234,6 +265,8 @@ export default defineComponent({
};
});
const api = useUserApi();
const { mealplans, actions, loading } = useMealplans(weekRange);
useRecipes(true, true);
@ -329,19 +362,15 @@ export default defineComponent({
newMeal.recipeId = undefined;
}
async function randomMeal(date: Date) {
// TODO: Refactor to use API call to get random recipe
const randomRecipe = allRecipes.value?.[Math.floor(Math.random() * allRecipes.value.length)];
if (!randomRecipe) return;
async function randomMeal(date: Date, type: PlanEntryType) {
const { data } = await api.mealplans.setRandom({
date: format(date, "yyyy-MM-dd"),
entryType: type,
});
newMeal.date = format(date, "yyyy-MM-dd");
newMeal.recipeId = randomRecipe.id;
console.log(newMeal.recipeId, randomRecipe.id);
await actions.createOne({ ...newMeal });
resetDialog();
if (data) {
actions.refreshAll();
}
}
return {

View file

@ -0,0 +1,179 @@
<template>
<v-container class="md-container">
<BasePageTitle divider>
<template #header>
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-cookbooks.svg')"></v-img>
</template>
<template #title> Meal Plan Rules </template>
Here you can set rules for auto selecting recipes for you meal plans. These rules are used by the server to
determine the random pool of recipes to select from when creating meal plans. Note that if rules have the same
day/type constraints then the categories of the rules will be merged. In practice, it's unnecessary to create
duplicate rules, but it's possible to do so.
</BasePageTitle>
<v-card>
<v-card-title class="headline"> New Rule </v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text>
When creating a new rule for a meal plan you can restrict the rule to be applicable for a specific day of the
week and/or a specific type of meal. To apply a rule to all days or all meal types you can set the rule to "Any"
which will apply it to all the possible values for the day and/or meal type.
<GroupMealPlanRuleForm
class="mt-2"
:day.sync="createData.day"
:entry-type.sync="createData.entryType"
:categories.sync="createData.categories"
:tags.sync="createData.tags"
/>
</v-card-text>
<v-card-actions class="justify-end">
<BaseButton create @click="createRule" />
</v-card-actions>
</v-card>
<section>
<BaseCardSectionTitle class="mt-10" title="Recipe Rules" />
<div>
<div v-for="(rule, idx) in allRules" :key="rule.id">
<v-card class="my-2">
<v-card-title>
{{ rule.day }} - {{ rule.entryType }}
<span class="ml-auto">
<BaseButtonGroup
:buttons="[
{
icon: $globals.icons.edit,
text: $t('general.edit'),
event: 'edit',
},
{
icon: $globals.icons.delete,
text: $t('general.delete'),
event: 'delete',
},
]"
@delete="deleteRule(rule.id)"
@edit="toggleEditState(rule.id)"
/>
</span>
</v-card-title>
<v-card-text>
<template v-if="!editState[rule.id]">
<div>Categories: {{ rule.categories.map((c) => c.name).join(", ") }}</div>
<div>Tags: {{ rule.tags.map((t) => t.name).join(", ") }}</div>
</template>
<template v-else>
<GroupMealPlanRuleForm
:day.sync="allRules[idx].day"
:entry-type.sync="allRules[idx].entryType"
:categories.sync="allRules[idx].categories"
:tags.sync="allRules[idx].tags"
/>
<div class="d-flex justify-end">
<BaseButton update @click="updateRule(rule)" />
</div>
</template>
</v-card-text>
</v-card>
</div>
</div>
</section>
</v-container>
</template>
<script lang="ts">
import { defineComponent, ref, useAsync } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
import { PlanRulesCreate, PlanRulesOut } from "~/types/api-types/meal-plan";
import GroupMealPlanRuleForm from "~/components/Domain/Group/GroupMealPlanRuleForm.vue";
import { useAsyncKey } from "~/composables/use-utils";
export default defineComponent({
components: {
GroupMealPlanRuleForm,
},
props: {
value: {
type: Boolean,
default: false,
},
},
setup() {
const api = useUserApi();
// ======================================================
// Manage All
const editState = ref<{ [key: string]: boolean }>({});
const allRules = ref<PlanRulesOut[]>([]);
function toggleEditState(id: string) {
editState.value[id] = !editState.value[id];
editState.value = { ...editState.value };
}
async function refreshAll() {
const { data } = await api.mealplanRules.getAll();
if (data) {
allRules.value = data;
}
}
useAsync(async () => {
await refreshAll();
}, useAsyncKey());
// ======================================================
// Creating Rules
const createData = ref<PlanRulesCreate>({
entryType: "unset",
day: "unset",
categories: [],
tags: [],
});
async function createRule() {
const { data } = await api.mealplanRules.createOne(createData.value);
if (data) {
refreshAll();
createData.value = {
entryType: "unset",
day: "unset",
categories: [],
tags: [],
};
}
}
async function deleteRule(ruleId: string) {
const { data } = await api.mealplanRules.deleteOne(ruleId);
if (data) {
refreshAll();
}
}
async function updateRule(rule: PlanRulesOut) {
const { data } = await api.mealplanRules.updateOne(rule.id, rule);
if (data) {
refreshAll();
toggleEditState(rule.id);
}
}
return {
allRules,
createData,
createRule,
deleteRule,
editState,
updateRule,
toggleEditState,
};
},
head: {
title: "Meal Plan Settings",
},
});
</script>

View file

@ -7,16 +7,6 @@
<template #title> Group Settings </template>
These items are shared within your group. Editing one of them will change it for the whole group!
</BasePageTitle>
<section>
<BaseCardSectionTitle title="Mealplan Categories">
Set the categories below for the ones that you want to be included in your mealplan random generation.
</BaseCardSectionTitle>
<DomainRecipeCategoryTagSelector v-if="categories" v-model="categories" />
<v-card-actions>
<v-spacer></v-spacer>
<BaseButton save @click="actions.updateAll()" />
</v-card-actions>
</section>
<section v-if="group">
<BaseCardSectionTitle class="mt-10" title="Group Preferences"></BaseCardSectionTitle>
@ -82,14 +72,13 @@
</section>
</v-container>
</template>
<script lang="ts">
import { defineComponent, useContext } from "@nuxtjs/composition-api";
import { useGroupCategories, useGroupSelf } from "~/composables/use-groups";
import { useGroupSelf } from "~/composables/use-groups";
export default defineComponent({
setup() {
const { categories, actions } = useGroupCategories();
const { group, actions: groupActions } = useGroupSelf();
const { i18n } = useContext();
@ -126,8 +115,6 @@ export default defineComponent({
];
return {
categories,
actions,
group,
groupActions,
allDays,
@ -140,5 +127,3 @@ export default defineComponent({
},
});
</script>

View file

@ -89,7 +89,7 @@ export interface Recipe {
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeTag[];
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;
@ -107,14 +107,20 @@ export interface Recipe {
};
comments?: RecipeCommentOut[];
}
export interface RecipeCategory {
id?: number;
name: string;
slug: string;
}
export interface RecipeTag {
id?: number;
name: string;
slug: string;
}
export interface RecipeTool {
id?: number;
name: string;
slug: string;
id?: number;
onHand?: boolean;
}
export interface RecipeIngredient {
@ -143,8 +149,8 @@ export interface IngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
id: number;
label?: MultiPurposeLabelSummary;
}
export interface MultiPurposeLabelSummary {
name: string;
@ -156,7 +162,6 @@ export interface CreateIngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
}
export interface RecipeStep {
id?: string;

View file

@ -45,7 +45,7 @@ export interface Recipe {
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeTag[];
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;
@ -63,14 +63,20 @@ export interface Recipe {
};
comments?: RecipeCommentOut[];
}
export interface RecipeCategory {
id?: number;
name: string;
slug: string;
}
export interface RecipeTag {
id?: number;
name: string;
slug: string;
}
export interface RecipeTool {
id?: number;
name: string;
slug: string;
id?: number;
onHand?: boolean;
}
export interface RecipeIngredient {
@ -99,8 +105,8 @@ export interface IngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
id: number;
label?: MultiPurposeLabelSummary;
}
export interface MultiPurposeLabelSummary {
name: string;
@ -112,7 +118,6 @@ export interface CreateIngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
}
export interface RecipeStep {
id?: string;

View file

@ -180,8 +180,8 @@ export interface IngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
id: number;
label?: MultiPurposeLabelSummary;
}
export interface MultiPurposeLabelSummary {
name: string;
@ -234,7 +234,7 @@ export interface RecipeSummary {
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeTag[];
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;
@ -243,14 +243,20 @@ export interface RecipeSummary {
dateAdded?: string;
dateUpdated?: string;
}
export interface RecipeCategory {
id?: number;
name: string;
slug: string;
}
export interface RecipeTag {
id?: number;
name: string;
slug: string;
}
export interface RecipeTool {
id?: number;
name: string;
slug: string;
id?: number;
onHand?: boolean;
}
export interface RecipeIngredient {
@ -272,7 +278,6 @@ export interface CreateIngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
}
export interface SaveInviteToken {
usesLeft: number;

View file

@ -5,8 +5,19 @@
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "snack";
export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "side";
export type PlanRulesDay = "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | "unset";
export type PlanRulesType = "breakfast" | "lunch" | "dinner" | "unset";
export interface Category {
id: number;
name: string;
slug: string;
}
export interface CreatRandomEntry {
date: string;
entryType?: PlanEntryType & string;
}
export interface CreatePlanEntry {
date: string;
entryType?: PlanEntryType & string;
@ -48,6 +59,32 @@ export interface MealPlanOut {
id: number;
shoppingList?: number;
}
export interface PlanRulesCreate {
day?: PlanRulesDay & string;
entryType?: PlanRulesType & string;
categories?: Category[];
tags?: Tag[];
}
export interface Tag {
id: number;
name: string;
slug: string;
}
export interface PlanRulesOut {
day?: PlanRulesDay & string;
entryType?: PlanRulesType & string;
categories?: Category[];
tags?: Tag[];
groupId: string;
id: string;
}
export interface PlanRulesSave {
day?: PlanRulesDay & string;
entryType?: PlanRulesType & string;
categories?: Category[];
tags?: Tag[];
groupId: string;
}
export interface ReadPlanEntry {
date: string;
entryType?: PlanEntryType & string;
@ -71,7 +108,7 @@ export interface RecipeSummary {
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeTag[];
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;
@ -80,14 +117,20 @@ export interface RecipeSummary {
dateAdded?: string;
dateUpdated?: string;
}
export interface RecipeCategory {
id?: number;
name: string;
slug: string;
}
export interface RecipeTag {
id?: number;
name: string;
slug: string;
}
export interface RecipeTool {
id?: number;
name: string;
slug: string;
id?: number;
onHand?: boolean;
}
export interface RecipeIngredient {
@ -116,8 +159,8 @@ export interface IngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
id: number;
label?: MultiPurposeLabelSummary;
}
export interface MultiPurposeLabelSummary {
name: string;
@ -129,7 +172,6 @@ export interface CreateIngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
}
export interface SavePlanEntry {
date: string;

View file

@ -42,13 +42,6 @@ export interface CreateIngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
export interface CreateIngredientUnit {
name: string;
@ -65,10 +58,12 @@ export interface CreateRecipeBulk {
tags?: RecipeTag[];
}
export interface RecipeCategory {
id?: number;
name: string;
slug: string;
}
export interface RecipeTag {
id?: number;
name: string;
slug: string;
}
@ -100,8 +95,14 @@ export interface IngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
id: number;
label?: MultiPurposeLabelSummary;
}
export interface MultiPurposeLabelSummary {
name: string;
color?: string;
groupId: string;
id: string;
}
/**
* A list of ingredient references.
@ -160,7 +161,7 @@ export interface Recipe {
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeTag[];
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;
@ -179,9 +180,9 @@ export interface Recipe {
comments?: RecipeCommentOut[];
}
export interface RecipeTool {
id?: number;
name: string;
slug: string;
id?: number;
onHand?: boolean;
}
export interface RecipeStep {
@ -281,7 +282,7 @@ export interface RecipeSummary {
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeTag[];
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;

View file

@ -121,7 +121,7 @@ export interface RecipeSummary {
cookTime?: string;
performTime?: string;
description?: string;
recipeCategory?: RecipeTag[];
recipeCategory?: RecipeCategory[];
tags?: RecipeTag[];
tools?: RecipeTool[];
rating?: number;
@ -130,14 +130,20 @@ export interface RecipeSummary {
dateAdded?: string;
dateUpdated?: string;
}
export interface RecipeCategory {
id?: number;
name: string;
slug: string;
}
export interface RecipeTag {
id?: number;
name: string;
slug: string;
}
export interface RecipeTool {
id?: number;
name: string;
slug: string;
id?: number;
onHand?: boolean;
}
export interface RecipeIngredient {
@ -166,8 +172,8 @@ export interface IngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
id: number;
label?: MultiPurposeLabelSummary;
}
export interface MultiPurposeLabelSummary {
name: string;
@ -179,7 +185,6 @@ export interface CreateIngredientFood {
name: string;
description?: string;
labelId?: string;
label?: MultiPurposeLabelSummary;
}
export interface ResetPassword {
token: string;

View file

@ -3,6 +3,7 @@ export interface Icon {
primary: string;
// General
bolwMixOutline: string;
foods: string;
units: string;
alert: string;

View file

@ -104,6 +104,7 @@ import {
mdiRefresh,
mdiArrowRightBold,
mdiChevronRight,
mdiBowlMixOutline,
} from "@mdi/js";
export const icons = {
@ -111,6 +112,7 @@ export const icons = {
primary: mdiSilverwareVariant,
// General
bolwMixOutline: mdiBowlMixOutline,
foods: mdiFoodApple,
units: mdiBeakerOutline,
alert: mdiAlert,