mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-24 01:12:54 -04:00
wip: recipe count for organizers
This commit is contained in:
parent
8ec60668e6
commit
01d335f73d
@ -47,3 +47,21 @@
|
|||||||
.v-card__title {
|
.v-card__title {
|
||||||
word-break: normal !important;
|
word-break: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-cols-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
grid-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 960px) {
|
||||||
|
.grid-cols-3 {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.grid-cols-3 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,24 +24,32 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<BaseButton create @click="dialog = true" />
|
<BaseButton create @click="dialog = true" />
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<section v-for="(itms, key, idx) in itemsSorted" :key="'header' + idx" :class="idx === 1 ? null : 'my-4'">
|
<section v-for="(itms, key, idx) in itemsSorted" :key="`header-${idx}`" :class="idx === 1 ? null : 'my-4'">
|
||||||
<BaseCardSectionTitle :title="key"> </BaseCardSectionTitle>
|
<BaseCardSectionTitle :title="key" />
|
||||||
<v-row>
|
<div class="grid-cols-3">
|
||||||
<v-col v-for="(item, index) in itms" :key="'cat' + index" cols="12" :sm="12" :md="6" :lg="4" :xl="3">
|
|
||||||
<v-card class="left-border" hover :to="`/recipes/${itemType}/${item.slug}`">
|
<v-card
|
||||||
|
v-for="(item, index) in itms"
|
||||||
|
:key="`cat-${index}`"
|
||||||
|
class="left-border"
|
||||||
|
hover
|
||||||
|
:to="`/recipes/${itemType}/${item.slug}`"
|
||||||
|
>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-icon>
|
<v-chip v-if="item.count != undefined" small class="ml-auto accent">
|
||||||
|
{{ item.count }}
|
||||||
|
</v-chip>
|
||||||
|
<v-icon v-else>
|
||||||
{{ icon }}
|
{{ icon }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
<v-card-title class="py-1">
|
<v-card-title class="py-1 w-full">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<ContextMenu :items="[presets.delete]" @delete="confirmDelete(item)" />
|
<ContextMenu :items="[presets.delete]" @delete="confirmDelete(item)" />
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</div>
|
||||||
</v-row>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -56,6 +64,7 @@ interface GenericItem {
|
|||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -80,7 +89,7 @@ export default defineComponent({
|
|||||||
// =================================================================
|
// =================================================================
|
||||||
// Sorted Items
|
// Sorted Items
|
||||||
const itemsSorted = computed(() => {
|
const itemsSorted = computed(() => {
|
||||||
const byLetter: { [key: string]: Array<GenericItem> } = {};
|
const byLetter: Record<string, Array<GenericItem>> = {};
|
||||||
|
|
||||||
if (!props.items) return byLetter;
|
if (!props.items) return byLetter;
|
||||||
|
|
||||||
@ -133,3 +142,9 @@ export default defineComponent({
|
|||||||
head: {},
|
head: {},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from mealie.db.models.group import Group, GroupMealPlan, ReportEntryModel, ReportModel
|
from mealie.db.models.group import Group, GroupMealPlan, ReportEntryModel, ReportModel
|
||||||
@ -47,11 +48,12 @@ from mealie.schema.labels import MultiPurposeLabelOut
|
|||||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||||
from mealie.schema.meal_plan.plan_rules import PlanRulesOut
|
from mealie.schema.meal_plan.plan_rules import PlanRulesOut
|
||||||
from mealie.schema.recipe import Recipe, RecipeCommentOut, RecipeToolOut
|
from mealie.schema.recipe import Recipe, RecipeCommentOut, RecipeToolOut
|
||||||
from mealie.schema.recipe.recipe_category import CategoryOut, TagOut
|
from mealie.schema.recipe.recipe_category import CategoryCount, CategoryOut, TagCount, TagOut
|
||||||
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit
|
from mealie.schema.recipe.recipe_ingredient import IngredientFood, IngredientUnit
|
||||||
from mealie.schema.recipe.recipe_share_token import RecipeShareToken
|
from mealie.schema.recipe.recipe_share_token import RecipeShareToken
|
||||||
from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventOut
|
from mealie.schema.recipe.recipe_timeline_events import RecipeTimelineEventOut
|
||||||
from mealie.schema.reports.reports import ReportEntryOut, ReportOut
|
from mealie.schema.reports.reports import ReportEntryOut, ReportOut
|
||||||
|
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
|
||||||
from mealie.schema.server import ServerTask
|
from mealie.schema.server import ServerTask
|
||||||
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser
|
from mealie.schema.user import GroupInDB, LongLiveTokenInDB, PrivateUser
|
||||||
from mealie.schema.user.user_passwords import PrivatePasswordResetToken
|
from mealie.schema.user.user_passwords import PrivatePasswordResetToken
|
||||||
@ -73,11 +75,57 @@ class RepositoryCategories(RepositoryGeneric[CategoryOut, Category]):
|
|||||||
def get_empty(self):
|
def get_empty(self):
|
||||||
return self.session.query(Category).filter(~Category.recipes.any()).all()
|
return self.session.query(Category).filter(~Category.recipes.any()).all()
|
||||||
|
|
||||||
|
def get_all_count_recipes(self, pagination: PaginationQuery) -> PaginationBase[TagCount]:
|
||||||
|
q = self.session.query(Category, func.count(RecipeModel.id).filter(RecipeModel.recipe_category)).group_by(
|
||||||
|
Category.id
|
||||||
|
)
|
||||||
|
|
||||||
|
fltr = self._filter_builder()
|
||||||
|
q = q.filter_by(**fltr)
|
||||||
|
q, count, total_pages = self.add_pagination_to_query(q, pagination)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data: list[tuple[Category, int]] = q.all()
|
||||||
|
except Exception as e:
|
||||||
|
self._log_exception(e)
|
||||||
|
self.session.rollback()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
return PaginationBase(
|
||||||
|
page=pagination.page,
|
||||||
|
per_page=pagination.per_page,
|
||||||
|
total=count,
|
||||||
|
total_pages=total_pages,
|
||||||
|
items=[CategoryCount(**s[0].__dict__, count=s[1]) for s in data],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RepositoryTags(RepositoryGeneric[TagOut, Tag]):
|
class RepositoryTags(RepositoryGeneric[TagOut, Tag]):
|
||||||
def get_empty(self):
|
def get_empty(self):
|
||||||
return self.session.query(Tag).filter(~Tag.recipes.any()).all()
|
return self.session.query(Tag).filter(~Tag.recipes.any()).all()
|
||||||
|
|
||||||
|
def get_all_count_recipes(self, pagination: PaginationQuery) -> PaginationBase[TagCount]:
|
||||||
|
q = self.session.query(self.model, func.count(RecipeModel.id)).join(RecipeModel.tags).group_by(self.model)
|
||||||
|
|
||||||
|
fltr = self._filter_builder()
|
||||||
|
q = q.filter_by(**fltr)
|
||||||
|
q, count, total_pages = self.add_pagination_to_query(q, pagination)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data: list[tuple[Tag, int]] = q.all()
|
||||||
|
except Exception as e:
|
||||||
|
self._log_exception(e)
|
||||||
|
self.session.rollback()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
return PaginationBase(
|
||||||
|
page=pagination.page,
|
||||||
|
per_page=pagination.per_page,
|
||||||
|
total=count,
|
||||||
|
total_pages=total_pages,
|
||||||
|
items=[TagCount(**s[0].__dict__, count=s[1]) for s in data],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AllRepositories:
|
class AllRepositories:
|
||||||
def __init__(self, session: Session) -> None:
|
def __init__(self, session: Session) -> None:
|
||||||
|
@ -7,9 +7,9 @@ from mealie.routes._base import BaseCrudController, controller
|
|||||||
from mealie.routes._base.mixins import HttpRepo
|
from mealie.routes._base.mixins import HttpRepo
|
||||||
from mealie.schema import mapper
|
from mealie.schema import mapper
|
||||||
from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
|
from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
|
||||||
from mealie.schema.recipe.recipe import RecipeCategory, RecipeCategoryPagination
|
from mealie.schema.recipe.recipe import RecipeCategory
|
||||||
from mealie.schema.recipe.recipe_category import CategoryBase, CategorySave
|
from mealie.schema.recipe.recipe_category import CategoryBase, CategoryCount, CategorySave
|
||||||
from mealie.schema.response.pagination import PaginationQuery
|
from mealie.schema.response.pagination import PaginationBase, PaginationQuery
|
||||||
from mealie.services import urls
|
from mealie.services import urls
|
||||||
from mealie.services.event_bus_service.event_types import EventCategoryData, EventOperation, EventTypes
|
from mealie.services.event_bus_service.event_types import EventCategoryData, EventOperation, EventTypes
|
||||||
|
|
||||||
@ -37,13 +37,10 @@ class RecipeCategoryController(BaseCrudController):
|
|||||||
def mixins(self):
|
def mixins(self):
|
||||||
return HttpRepo(self.repo, self.logger)
|
return HttpRepo(self.repo, self.logger)
|
||||||
|
|
||||||
@router.get("", response_model=RecipeCategoryPagination)
|
@router.get("", response_model=PaginationBase[CategoryCount])
|
||||||
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
||||||
"""Returns a list of available categories in the database"""
|
"""Returns a list of available categories in the database"""
|
||||||
response = self.repo.page_all(
|
response = self.repo.get_all_count_recipes(pagination=q)
|
||||||
pagination=q,
|
|
||||||
override=RecipeCategory,
|
|
||||||
)
|
|
||||||
|
|
||||||
response.set_pagination_guides(router.url_path_for("get_all"), q.dict())
|
response.set_pagination_guides(router.url_path_for("get_all"), q.dict())
|
||||||
return response
|
return response
|
||||||
|
@ -3,11 +3,12 @@ from functools import cached_property
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
|
from mealie.repos.repository_factory import RepositoryTags
|
||||||
from mealie.routes._base import BaseCrudController, controller
|
from mealie.routes._base import BaseCrudController, controller
|
||||||
from mealie.routes._base.mixins import HttpRepo
|
from mealie.routes._base.mixins import HttpRepo
|
||||||
from mealie.schema import mapper
|
from mealie.schema import mapper
|
||||||
from mealie.schema.recipe import RecipeTagResponse, TagIn
|
from mealie.schema.recipe import RecipeTagResponse, TagIn
|
||||||
from mealie.schema.recipe.recipe import RecipeTag, RecipeTagPagination
|
from mealie.schema.recipe.recipe import RecipeTagPagination
|
||||||
from mealie.schema.recipe.recipe_category import TagSave
|
from mealie.schema.recipe.recipe_category import TagSave
|
||||||
from mealie.schema.response.pagination import PaginationQuery
|
from mealie.schema.response.pagination import PaginationQuery
|
||||||
from mealie.services import urls
|
from mealie.services import urls
|
||||||
@ -19,7 +20,7 @@ router = APIRouter(prefix="/tags", tags=["Organizer: Tags"])
|
|||||||
@controller(router)
|
@controller(router)
|
||||||
class TagController(BaseCrudController):
|
class TagController(BaseCrudController):
|
||||||
@cached_property
|
@cached_property
|
||||||
def repo(self):
|
def repo(self) -> RepositoryTags:
|
||||||
return self.repos.tags.by_group(self.group_id)
|
return self.repos.tags.by_group(self.group_id)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
@ -29,10 +30,7 @@ class TagController(BaseCrudController):
|
|||||||
@router.get("", response_model=RecipeTagPagination)
|
@router.get("", response_model=RecipeTagPagination)
|
||||||
async def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
async def get_all(self, q: PaginationQuery = Depends(PaginationQuery)):
|
||||||
"""Returns a list of available tags in the database"""
|
"""Returns a list of available tags in the database"""
|
||||||
response = self.repo.page_all(
|
response = self.repo.get_all_count_recipes(pagination=q)
|
||||||
pagination=q,
|
|
||||||
override=RecipeTag,
|
|
||||||
)
|
|
||||||
|
|
||||||
response.set_pagination_guides(router.url_path_for("get_all"), q.dict())
|
response.set_pagination_guides(router.url_path_for("get_all"), q.dict())
|
||||||
return response
|
return response
|
||||||
|
@ -4,6 +4,7 @@ from sqlalchemy.orm.session import Session
|
|||||||
from mealie.db.db_setup import generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from mealie.repos.all_repositories import get_repositories
|
from mealie.repos.all_repositories import get_repositories
|
||||||
from mealie.schema.recipe import RecipeSummary
|
from mealie.schema.recipe import RecipeSummary
|
||||||
|
from mealie.schema.response.pagination import PaginationQuery
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@ -18,3 +19,13 @@ async def get_untagged_recipes(count: bool = False, session: Session = Depends(g
|
|||||||
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
async def get_uncategorized_recipes(count: bool = False, session: Session = Depends(generate_session)):
|
||||||
db = get_repositories(session)
|
db = get_repositories(session)
|
||||||
return db.recipes.count_uncategorized(count=count, override_schema=RecipeSummary)
|
return db.recipes.count_uncategorized(count=count, override_schema=RecipeSummary)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/summar/whatever")
|
||||||
|
async def get_whatever(session: Session = Depends(generate_session)):
|
||||||
|
db = get_repositories(session)
|
||||||
|
payload = db.tags.get_all_count_recipes(
|
||||||
|
PaginationQuery(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return payload
|
||||||
|
@ -5,7 +5,6 @@ from .recipe import (
|
|||||||
CreateRecipeByUrlBulk,
|
CreateRecipeByUrlBulk,
|
||||||
Recipe,
|
Recipe,
|
||||||
RecipeCategory,
|
RecipeCategory,
|
||||||
RecipeCategoryPagination,
|
|
||||||
RecipePagination,
|
RecipePagination,
|
||||||
RecipePaginationQuery,
|
RecipePaginationQuery,
|
||||||
RecipeSummary,
|
RecipeSummary,
|
||||||
|
@ -53,7 +53,7 @@ class TagBase(CategoryBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TagOut(TagSave):
|
class TagOut(TagIn):
|
||||||
id: UUID4
|
id: UUID4
|
||||||
slug: str
|
slug: str
|
||||||
|
|
||||||
@ -61,6 +61,17 @@ class TagOut(TagSave):
|
|||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
|
class TagCount(TagOut):
|
||||||
|
count: int = 0
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryCount(TagCount):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class RecipeTagResponse(RecipeCategoryResponse):
|
class RecipeTagResponse(RecipeCategoryResponse):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user