mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-04 13:05:21 +02:00
Feature/UI updates (#990)
* titleCase utility * update rules ui * order by date_added * fix error on page refresh * fix health checks * fix cookbook return values
This commit is contained in:
parent
177a430d8c
commit
2211174636
6 changed files with 43 additions and 20 deletions
|
@ -131,7 +131,7 @@ ENV APP_PORT=9000
|
||||||
|
|
||||||
EXPOSE ${APP_PORT}
|
EXPOSE ${APP_PORT}
|
||||||
|
|
||||||
HEALTHCHECK CMD curl -f http://localhost:${APP_PORT} || exit 1
|
HEALTHCHECK CMD curl -f http://localhost:${APP_PORT}/docs || exit 1
|
||||||
|
|
||||||
RUN chmod +x $MEALIE_HOME/mealie/run.sh
|
RUN chmod +x $MEALIE_HOME/mealie/run.sh
|
||||||
ENTRYPOINT $MEALIE_HOME/mealie/run.sh
|
ENTRYPOINT $MEALIE_HOME/mealie/run.sh
|
||||||
|
|
|
@ -4,6 +4,13 @@ export const useAsyncKey = function () {
|
||||||
return String(Date.now());
|
return String(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const titleCase = function (str: string) {
|
||||||
|
return str
|
||||||
|
.split(" ")
|
||||||
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(" ");
|
||||||
|
};
|
||||||
|
|
||||||
export function detectServerBaseUrl(req?: IncomingMessage | null) {
|
export function detectServerBaseUrl(req?: IncomingMessage | null) {
|
||||||
if (!req || req === undefined) {
|
if (!req || req === undefined) {
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-cookbooks.svg')"></v-img>
|
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-cookbooks.svg')"></v-img>
|
||||||
</template>
|
</template>
|
||||||
<template #title> Meal Plan Rules </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
|
You can create 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
|
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
|
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.
|
duplicate rules, but it's possible to do so.
|
||||||
|
@ -36,9 +36,10 @@
|
||||||
<BaseCardSectionTitle class="mt-10" title="Recipe Rules" />
|
<BaseCardSectionTitle class="mt-10" title="Recipe Rules" />
|
||||||
<div>
|
<div>
|
||||||
<div v-for="(rule, idx) in allRules" :key="rule.id">
|
<div v-for="(rule, idx) in allRules" :key="rule.id">
|
||||||
<v-card class="my-2">
|
<v-card class="my-2 left-border">
|
||||||
<v-card-title>
|
<v-card-title class="headline pb-1">
|
||||||
{{ rule.day }} - {{ rule.entryType }}
|
{{ rule.day === "unset" ? "Applies to all days" : `Applies on ${rule.day}s` }}
|
||||||
|
{{ rule.entryType === "unset" ? "for all meal types" : ` for ${rule.entryType} meal types` }}
|
||||||
<span class="ml-auto">
|
<span class="ml-auto">
|
||||||
<BaseButtonGroup
|
<BaseButtonGroup
|
||||||
:buttons="[
|
:buttons="[
|
||||||
|
@ -60,8 +61,15 @@
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<template v-if="!editState[rule.id]">
|
<template v-if="!editState[rule.id]">
|
||||||
<div>Categories: {{ rule.categories.map((c) => c.name).join(", ") }}</div>
|
<div v-if="rule.categories">
|
||||||
<div>Tags: {{ rule.tags.map((t) => t.name).join(", ") }}</div>
|
<h4 class="py-1">{{ $t("category.categories") }}:</h4>
|
||||||
|
<RecipeChips :items="rule.categories" is-category small />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="rule.tags">
|
||||||
|
<h4 class="py-1">{{ $t("tag.tags") }}:</h4>
|
||||||
|
<RecipeChips :items="rule.tags" :is-category="false" small />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<GroupMealPlanRuleForm
|
<GroupMealPlanRuleForm
|
||||||
|
@ -88,10 +96,12 @@ import { useUserApi } from "~/composables/api";
|
||||||
import { PlanRulesCreate, PlanRulesOut } from "~/types/api-types/meal-plan";
|
import { PlanRulesCreate, PlanRulesOut } from "~/types/api-types/meal-plan";
|
||||||
import GroupMealPlanRuleForm from "~/components/Domain/Group/GroupMealPlanRuleForm.vue";
|
import GroupMealPlanRuleForm from "~/components/Domain/Group/GroupMealPlanRuleForm.vue";
|
||||||
import { useAsyncKey } from "~/composables/use-utils";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
|
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
GroupMealPlanRuleForm,
|
GroupMealPlanRuleForm,
|
||||||
|
RecipeChips,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
|
|
|
@ -217,7 +217,7 @@
|
||||||
<!-- Debug Extras -->
|
<!-- Debug Extras -->
|
||||||
<section v-if="debugData && tab === 'debug'">
|
<section v-if="debugData && tab === 'debug'">
|
||||||
<v-checkbox v-model="debugTreeView" label="Tree View"></v-checkbox>
|
<v-checkbox v-model="debugTreeView" label="Tree View"></v-checkbox>
|
||||||
<VJsoneditor
|
<LazyRecipeJsonEditor
|
||||||
v-model="debugData"
|
v-model="debugData"
|
||||||
class="primary"
|
class="primary"
|
||||||
:options="{
|
:options="{
|
||||||
|
@ -322,11 +322,9 @@ import {
|
||||||
useRouter,
|
useRouter,
|
||||||
useContext,
|
useContext,
|
||||||
computed,
|
computed,
|
||||||
useRoute
|
useRoute,
|
||||||
} from "@nuxtjs/composition-api";
|
} from "@nuxtjs/composition-api";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
// @ts-ignore No Types for v-jsoneditor
|
|
||||||
import VJsoneditor from "v-jsoneditor";
|
|
||||||
import { useUserApi } from "~/composables/api";
|
import { useUserApi } from "~/composables/api";
|
||||||
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
|
import RecipeCategoryTagSelector from "~/components/Domain/Recipe/RecipeCategoryTagSelector.vue";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
|
@ -336,7 +334,7 @@ import { VForm } from "~/types/vuetify";
|
||||||
import { MenuItem } from "~/components/global/BaseOverflowButton.vue";
|
import { MenuItem } from "~/components/global/BaseOverflowButton.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { VJsoneditor, RecipeCategoryTagSelector },
|
components: { RecipeCategoryTagSelector },
|
||||||
setup() {
|
setup() {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
error: false,
|
error: false,
|
||||||
|
@ -397,7 +395,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const recipeUrl = computed({
|
const recipeUrl = computed({
|
||||||
set(recipe_import_url: string) {
|
set(recipe_import_url: string) {
|
||||||
recipe_import_url = recipe_import_url.trim()
|
recipe_import_url = recipe_import_url.trim();
|
||||||
router.replace({ query: { ...route.value.query, recipe_import_url } });
|
router.replace({ query: { ...route.value.query, recipe_import_url } });
|
||||||
},
|
},
|
||||||
get() {
|
get() {
|
||||||
|
@ -516,7 +514,6 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.force-white > a {
|
.force-white > a {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
|
|
|
@ -84,6 +84,7 @@ class RepositoryRecipes(RepositoryGeneric[Recipe, RecipeModel]):
|
||||||
self.session.query(RecipeModel)
|
self.session.query(RecipeModel)
|
||||||
.options(*args)
|
.options(*args)
|
||||||
.filter(RecipeModel.group_id == group_id)
|
.filter(RecipeModel.group_id == group_id)
|
||||||
|
.order_by(RecipeModel.date_added.desc())
|
||||||
.offset(start)
|
.offset(start)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.all()
|
.all()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
from mealie.core.exceptions import mealie_registered_exceptions
|
from mealie.core.exceptions import mealie_registered_exceptions
|
||||||
|
@ -9,10 +9,15 @@ from mealie.routes._base import BaseUserController, controller
|
||||||
from mealie.routes._base.mixins import CrudMixins
|
from mealie.routes._base.mixins import CrudMixins
|
||||||
from mealie.schema import mapper
|
from mealie.schema import mapper
|
||||||
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
||||||
|
from mealie.schema.recipe.recipe_category import RecipeCategoryResponse
|
||||||
|
|
||||||
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"])
|
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"])
|
||||||
|
|
||||||
|
|
||||||
|
class CookBookRecipeResponse(RecipeCookBook):
|
||||||
|
categories: list[RecipeCategoryResponse]
|
||||||
|
|
||||||
|
|
||||||
@controller(router)
|
@controller(router)
|
||||||
class GroupCookbookController(BaseUserController):
|
class GroupCookbookController(BaseUserController):
|
||||||
@cached_property
|
@cached_property
|
||||||
|
@ -54,12 +59,15 @@ class GroupCookbookController(BaseUserController):
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
@router.get("/{item_id}", response_model=RecipeCookBook)
|
@router.get("/{item_id}", response_model=CookBookRecipeResponse)
|
||||||
def get_one(self, item_id: UUID4 | str):
|
def get_one(self, item_id: UUID4 | str):
|
||||||
if isinstance(item_id, str):
|
match_attr = "slug" if isinstance(item_id, str) else "id"
|
||||||
self.mixins.get_one(item_id, key="slug")
|
book = self.repo.get_one(item_id, match_attr, override_schema=CookBookRecipeResponse)
|
||||||
else:
|
|
||||||
return self.mixins.get_one(item_id)
|
if book is None:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
return book
|
||||||
|
|
||||||
@router.put("/{item_id}", response_model=RecipeCookBook)
|
@router.put("/{item_id}", response_model=RecipeCookBook)
|
||||||
def update_one(self, item_id: str, data: CreateCookBook):
|
def update_one(self, item_id: str, data: CreateCookBook):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue