mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 06:23:34 -07:00
frontend category management
This commit is contained in:
parent
abcf40899f
commit
f000dffde2
13 changed files with 272 additions and 80 deletions
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-app>
|
||||
<v-app-bar dense app color="primary" dark class="d-print-none">
|
||||
<v-app-bar clipped-left dense app color="primary" dark class="d-print-none">
|
||||
<v-btn @click="$router.push('/')" icon>
|
||||
<v-icon size="40"> mdi-silverware-variant </v-icon>
|
||||
</v-btn>
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import { baseURL } from "./api-utils";
|
||||
import { apiReq } from "./api-utils";
|
||||
|
||||
const categoryBase = baseURL + "category/";
|
||||
const categoryBase = baseURL + "/recipes/categories";
|
||||
|
||||
const categoryURLs = {
|
||||
get_all: `${categoryBase}all`,
|
||||
};
|
||||
get_all: `${categoryBase}/all/`,
|
||||
get_category: (category) => `${categoryBase}/${category}/`,
|
||||
};
|
||||
|
||||
export default {
|
||||
async get_all() {
|
||||
let response = await apiReq.get(categoryURLs.get_all);
|
||||
return response;
|
||||
},
|
||||
}
|
||||
async get_all() {
|
||||
let response = await apiReq.get(categoryURLs.get_all);
|
||||
return response.data;
|
||||
},
|
||||
async get_recipes_in_category(category) {
|
||||
let response = await apiReq.get(categoryURLs.get_category(category));
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -119,36 +119,34 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
homeCategories: [],
|
||||
homeCategories: null,
|
||||
showLimit: null,
|
||||
categories: ["breakfast"],
|
||||
showRecent: true,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getOptions();
|
||||
},
|
||||
|
||||
computed: {
|
||||
categories() {
|
||||
return this.$store.getters.getCategories;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getOptions() {
|
||||
let options = this.$store.getters.getHomePageSettings;
|
||||
this.showLimit = options.showLimit;
|
||||
this.categories = options.categories;
|
||||
this.showRecent = options.showRecent;
|
||||
this.homeCategories = options.homeCategories;
|
||||
this.showLimit = this.$store.getters.getShowLimit;
|
||||
this.showRecent = this.$store.getters.getShowRecent;
|
||||
this.homeCategories = this.$store.getters.getHomeCategories;
|
||||
},
|
||||
deleteActiveCategory(index) {
|
||||
this.homeCategories.splice(index, 1);
|
||||
},
|
||||
saveSettings() {
|
||||
let payload = {
|
||||
showRecent: this.showRecent,
|
||||
showLimit: this.showLimit,
|
||||
categories: this.categories,
|
||||
homeCategories: this.homeCategories,
|
||||
};
|
||||
|
||||
this.$store.commit("setHomePageSettings", payload);
|
||||
console.log("Saving Settings");
|
||||
console.log(this.homeCategories);
|
||||
this.$store.commit("setShowRecent", this.showRecent);
|
||||
this.$store.commit("setShowLimit", this.showLimit);
|
||||
this.$store.commit("setHomeCategories", this.homeCategories);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -43,22 +43,14 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
categories: ["cat 1", "cat 2", "cat 3"],
|
||||
usedCategories: ["recent"],
|
||||
langOptions: [],
|
||||
selectedLang: "en",
|
||||
homeOptions: {
|
||||
recipesToShow: 10,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getOptions();
|
||||
},
|
||||
watch: {
|
||||
usedCategories() {
|
||||
console.log(this.usedCategories);
|
||||
},
|
||||
selectedLang() {
|
||||
this.$store.commit("setLang", this.selectedLang);
|
||||
},
|
||||
|
@ -66,6 +58,7 @@ export default {
|
|||
methods: {
|
||||
getOptions() {
|
||||
this.langOptions = this.$store.getters.getAllLangs;
|
||||
console.log(this.langOptions);
|
||||
this.selectedLang = this.$store.getters.getActiveLang;
|
||||
},
|
||||
},
|
||||
|
|
50
frontend/src/components/UI/CategorySidebar.vue
Normal file
50
frontend/src/components/UI/CategorySidebar.vue
Normal file
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<v-navigation-drawer width="175px" clipped app permanent expand-on-hover>
|
||||
<v-list nav dense>
|
||||
<v-list-item v-for="nav in links" :key="nav.title" link :to="nav.to">
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ nav.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
links: [
|
||||
{
|
||||
icon: "mdi-home",
|
||||
to: "/",
|
||||
title: "Home",
|
||||
},
|
||||
{
|
||||
icon: "mdi-view-module",
|
||||
to: "/recipes/all",
|
||||
title: "All Recipes",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allCategories() {
|
||||
return this.$store.getters.getCategories;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.allCategories.forEach(async (element) => {
|
||||
this.links.push({
|
||||
title: element,
|
||||
to: `/recipes/${element}`,
|
||||
icon: "mdi-tag",
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -3,7 +3,7 @@
|
|||
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
|
||||
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" text>
|
||||
<v-icon left> mdi-cloud-upload </v-icon>
|
||||
{{ $t('general.upload') }}
|
||||
{{ $t("general.upload") }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</template>
|
||||
|
@ -15,7 +15,6 @@ export default {
|
|||
url: String,
|
||||
},
|
||||
data: () => ({
|
||||
defaultButtonText: this.$t("general.upload"),
|
||||
file: null,
|
||||
isSelecting: false,
|
||||
}),
|
||||
|
|
43
frontend/src/pages/AllRecipesPage.vue
Normal file
43
frontend/src/pages/AllRecipesPage.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div>
|
||||
<CategorySidebar />
|
||||
<CardSection
|
||||
:sortable="true"
|
||||
title="All Recipes"
|
||||
:recipes="allRecipes"
|
||||
:card-limit="9999"
|
||||
@sort="sortAZ"
|
||||
@sort-recent="sortRecent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CardSection from "../components/UI/CardSection";
|
||||
import CategorySidebar from "../components/UI/CategorySidebar";
|
||||
export default {
|
||||
components: {
|
||||
CardSection,
|
||||
CategorySidebar,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
allRecipes() {
|
||||
return this.$store.getters.getRecentRecipes;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sortAZ() {
|
||||
this.allRecipes.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
},
|
||||
sortRecent() {
|
||||
this.allRecipes.sort((a, b) => (a.dateAdded > b.dateAdded ? -1 : 1));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
62
frontend/src/pages/CategoryPage.vue
Normal file
62
frontend/src/pages/CategoryPage.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<div>
|
||||
<CategorySidebar />
|
||||
<CardSection
|
||||
:sortable="true"
|
||||
:title="title"
|
||||
:recipes="recipes"
|
||||
:card-limit="9999"
|
||||
@sort="sortAZ"
|
||||
@sort-recent="sortRecent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../api";
|
||||
import CardSection from "../components/UI/CardSection";
|
||||
import CategorySidebar from "../components/UI/CategorySidebar";
|
||||
export default {
|
||||
components: {
|
||||
CardSection,
|
||||
CategorySidebar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: null,
|
||||
recipes: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentCategory() {
|
||||
return this.$route.params.category;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
async currentCategory() {
|
||||
this.getRecipes();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getRecipes();
|
||||
},
|
||||
methods: {
|
||||
async getRecipes() {
|
||||
let data = await api.categories.get_recipes_in_category(
|
||||
this.currentCategory
|
||||
);
|
||||
this.title = data.name;
|
||||
this.recipes = data.recipes;
|
||||
},
|
||||
sortAZ() {
|
||||
this.recipes.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
},
|
||||
sortRecent() {
|
||||
this.recipes.sort((a, b) => (a.dateAdded > b.dateAdded ? -1 : 1));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,18 +1,20 @@
|
|||
<template>
|
||||
<div>
|
||||
<CategorySidebar/>
|
||||
|
||||
<CardSection
|
||||
v-if="pageSettings.showRecent"
|
||||
v-if="showRecent"
|
||||
title="Recent"
|
||||
:recipes="recentRecipes"
|
||||
:card-limit="pageSettings.showLimit"
|
||||
:card-limit="showLimit"
|
||||
/>
|
||||
<CardSection
|
||||
:sortable="true"
|
||||
v-for="(section, index) in recipeByCategory"
|
||||
:key="index"
|
||||
:title="section.title"
|
||||
:title="section.name"
|
||||
:recipes="section.recipes"
|
||||
:card-limit="pageSettings.showLimit"
|
||||
:card-limit="showLimit"
|
||||
@sort="sortAZ(index)"
|
||||
@sort-recent="sortRecent(index)"
|
||||
/>
|
||||
|
@ -20,34 +22,43 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import api from "../api";
|
||||
import CardSection from "../components/UI/CardSection";
|
||||
import CategorySidebar from "../components/UI/CategorySidebar";
|
||||
export default {
|
||||
components: {
|
||||
CardSection,
|
||||
CategorySidebar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
recipeByCategory: [
|
||||
{
|
||||
title: "Title 1",
|
||||
recipes: this.$store.getters.getRecentRecipes,
|
||||
},
|
||||
{
|
||||
title: "Title 2",
|
||||
recipes: this.$store.getters.getRecentRecipes,
|
||||
},
|
||||
],
|
||||
recipeByCategory: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageSettings() {
|
||||
return this.$store.getters.getHomePageSettings;
|
||||
showRecent() {
|
||||
return this.$store.getters.getShowRecent;
|
||||
},
|
||||
showLimit() {
|
||||
return this.$store.getters.getShowLimit;
|
||||
},
|
||||
homeCategories() {
|
||||
return this.$store.getters.getHomeCategories;
|
||||
},
|
||||
recentRecipes() {
|
||||
return this.$store.getters.getRecentRecipes;
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.homeCategories.forEach(async (element) => {
|
||||
let recipes = await this.getRecipeByCategory(element);
|
||||
this.recipeByCategory.push(recipes);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
async getRecipeByCategory(category) {
|
||||
return await api.categories.get_recipes_in_category(category);
|
||||
},
|
||||
getRecentRecipes() {
|
||||
this.$store.dispatch("requestRecentRecipes");
|
||||
},
|
||||
|
|
|
@ -4,6 +4,8 @@ import SearchPage from "./pages/SearchPage";
|
|||
import RecipePage from "./pages/RecipePage";
|
||||
import RecipeNewPage from "./pages/RecipeNewPage";
|
||||
import SettingsPage from "./pages/SettingsPage";
|
||||
import AllRecipesPage from "./pages/AllRecipesPage";
|
||||
import CategoryPage from "./pages/CategoryPage";
|
||||
import MeaplPlanPage from "./pages/MealPlanPage";
|
||||
import MealPlanThisWeekPage from "./pages/MealPlanThisWeekPage";
|
||||
import api from "./api";
|
||||
|
@ -12,6 +14,8 @@ export const routes = [
|
|||
{ path: "/", component: HomePage },
|
||||
{ path: "/mealie", component: HomePage },
|
||||
{ path: "/search", component: SearchPage },
|
||||
{ path: "/recipes/all", component: AllRecipesPage },
|
||||
{ path: "/recipes/:category", component: CategoryPage },
|
||||
{ path: "/recipe/:recipe", component: RecipePage },
|
||||
{ path: "/new/", component: RecipeNewPage },
|
||||
{ path: "/settings/site", component: SettingsPage },
|
||||
|
|
44
frontend/src/store/modules/homePage.js
Normal file
44
frontend/src/store/modules/homePage.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import api from "../../api";
|
||||
|
||||
const state = {
|
||||
showRecent: true,
|
||||
showLimit: 9,
|
||||
categories: [],
|
||||
homeCategories: [],
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setShowRecent(state, payload) {
|
||||
state.showRecent = payload;
|
||||
},
|
||||
setShowLimit(state, payload) {
|
||||
state.showLimit = payload;
|
||||
},
|
||||
setCategories(state, payload) {
|
||||
state.categories = payload;
|
||||
},
|
||||
setHomeCategories(state, payload) {
|
||||
state.homeCategories = payload;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
async requestHomePageSettings() {
|
||||
let categories = await api.categories.get_all();
|
||||
this.commit("setCategories", categories);
|
||||
},
|
||||
};
|
||||
|
||||
const getters = {
|
||||
getShowRecent: (state) => state.showRecent,
|
||||
getShowLimit: (state) => state.showLimit,
|
||||
getCategories: (state) => state.categories,
|
||||
getHomeCategories: (state) => state.homeCategories,
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
};
|
|
@ -4,27 +4,23 @@ import api from "../api";
|
|||
import createPersistedState from "vuex-persistedstate";
|
||||
import userSettings from "./modules/userSettings";
|
||||
import language from "./modules/language";
|
||||
import homePage from "./modules/homePage";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
plugins: [
|
||||
createPersistedState({
|
||||
paths: ["userSettings", "language"],
|
||||
paths: ["userSettings", "language", "homePage"],
|
||||
}),
|
||||
],
|
||||
modules: {
|
||||
userSettings,
|
||||
language,
|
||||
homePage,
|
||||
},
|
||||
state: {
|
||||
// Home Page Settings
|
||||
homePageSettings: {
|
||||
showRecent: true,
|
||||
showLimit: 9,
|
||||
categories: [],
|
||||
homeCategories: [],
|
||||
},
|
||||
// Snackbar
|
||||
snackActive: false,
|
||||
snackText: "",
|
||||
|
@ -36,9 +32,6 @@ const store = new Vuex.Store({
|
|||
},
|
||||
|
||||
mutations: {
|
||||
setHomePageSettings(state, payload) {
|
||||
state.homePageSettings = payload;
|
||||
},
|
||||
setSnackBar(state, payload) {
|
||||
state.snackText = payload.text;
|
||||
state.snackType = payload.type;
|
||||
|
@ -67,26 +60,15 @@ const store = new Vuex.Store({
|
|||
|
||||
this.commit("setRecentRecipes", payload);
|
||||
},
|
||||
|
||||
async requestHomePageSettings() {
|
||||
// TODO: Query Backend for Categories
|
||||
this.commit("setHomePageSettings", {
|
||||
showRecent: true,
|
||||
showLimit: 9,
|
||||
categories: ["breakfast", "lunch", "dinner"],
|
||||
homeCategories: [],
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
getters: {
|
||||
//
|
||||
getSnackText: state => state.snackText,
|
||||
getSnackActive: state => state.snackActive,
|
||||
getSnackType: state => state.snackType,
|
||||
getSnackText: (state) => state.snackText,
|
||||
getSnackActive: (state) => state.snackActive,
|
||||
getSnackType: (state) => state.snackType,
|
||||
|
||||
getRecentRecipes: (state) => state.recentRecipes,
|
||||
getHomePageSettings: (state) => state.homePageSettings,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -23,11 +23,12 @@ from utils.logger import logger
|
|||
|
||||
"""
|
||||
TODO:
|
||||
- [ ] Fix Duplicate Category
|
||||
- [ ] Fix Duplicate Tags
|
||||
- [ ] Add Endpoint
|
||||
- [x] Fix Duplicate Category
|
||||
- [x] Fix Duplicate Tags
|
||||
- [ ] New Endpoints
|
||||
- [x] Tag Endpoints
|
||||
- [x] Category Endpoints
|
||||
- [ ] Endpoint Tests
|
||||
- [ ] Setup Database Migrations
|
||||
- [ ] Finish Frontend Category Management
|
||||
- [ ] Ingredient Drag-Drop / Reorder
|
||||
- [ ] Refactor Endpoints
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue