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>
|
<template>
|
||||||
<v-app>
|
<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-btn @click="$router.push('/')" icon>
|
||||||
<v-icon size="40"> mdi-silverware-variant </v-icon>
|
<v-icon size="40"> mdi-silverware-variant </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
import { baseURL } from "./api-utils";
|
import { baseURL } from "./api-utils";
|
||||||
import { apiReq } from "./api-utils";
|
import { apiReq } from "./api-utils";
|
||||||
|
|
||||||
const categoryBase = baseURL + "category/";
|
const categoryBase = baseURL + "/recipes/categories";
|
||||||
|
|
||||||
const categoryURLs = {
|
const categoryURLs = {
|
||||||
get_all: `${categoryBase}all`,
|
get_all: `${categoryBase}/all/`,
|
||||||
};
|
get_category: (category) => `${categoryBase}/${category}/`,
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async get_all() {
|
async get_all() {
|
||||||
let response = await apiReq.get(categoryURLs.get_all);
|
let response = await apiReq.get(categoryURLs.get_all);
|
||||||
return response;
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
homeCategories: [],
|
homeCategories: null,
|
||||||
showLimit: null,
|
showLimit: null,
|
||||||
categories: ["breakfast"],
|
|
||||||
showRecent: true,
|
showRecent: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getOptions();
|
this.getOptions();
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
categories() {
|
||||||
|
return this.$store.getters.getCategories;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getOptions() {
|
getOptions() {
|
||||||
let options = this.$store.getters.getHomePageSettings;
|
this.showLimit = this.$store.getters.getShowLimit;
|
||||||
this.showLimit = options.showLimit;
|
this.showRecent = this.$store.getters.getShowRecent;
|
||||||
this.categories = options.categories;
|
this.homeCategories = this.$store.getters.getHomeCategories;
|
||||||
this.showRecent = options.showRecent;
|
|
||||||
this.homeCategories = options.homeCategories;
|
|
||||||
},
|
},
|
||||||
deleteActiveCategory(index) {
|
deleteActiveCategory(index) {
|
||||||
this.homeCategories.splice(index, 1);
|
this.homeCategories.splice(index, 1);
|
||||||
},
|
},
|
||||||
saveSettings() {
|
saveSettings() {
|
||||||
let payload = {
|
console.log("Saving Settings");
|
||||||
showRecent: this.showRecent,
|
console.log(this.homeCategories);
|
||||||
showLimit: this.showLimit,
|
this.$store.commit("setShowRecent", this.showRecent);
|
||||||
categories: this.categories,
|
this.$store.commit("setShowLimit", this.showLimit);
|
||||||
homeCategories: this.homeCategories,
|
this.$store.commit("setHomeCategories", this.homeCategories);
|
||||||
};
|
|
||||||
|
|
||||||
this.$store.commit("setHomePageSettings", payload);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,22 +43,14 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
categories: ["cat 1", "cat 2", "cat 3"],
|
|
||||||
usedCategories: ["recent"],
|
|
||||||
langOptions: [],
|
langOptions: [],
|
||||||
selectedLang: "en",
|
selectedLang: "en",
|
||||||
homeOptions: {
|
|
||||||
recipesToShow: 10,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getOptions();
|
this.getOptions();
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
usedCategories() {
|
|
||||||
console.log(this.usedCategories);
|
|
||||||
},
|
|
||||||
selectedLang() {
|
selectedLang() {
|
||||||
this.$store.commit("setLang", this.selectedLang);
|
this.$store.commit("setLang", this.selectedLang);
|
||||||
},
|
},
|
||||||
|
@ -66,6 +58,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
getOptions() {
|
getOptions() {
|
||||||
this.langOptions = this.$store.getters.getAllLangs;
|
this.langOptions = this.$store.getters.getAllLangs;
|
||||||
|
console.log(this.langOptions);
|
||||||
this.selectedLang = this.$store.getters.getActiveLang;
|
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" />
|
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
|
||||||
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" text>
|
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" text>
|
||||||
<v-icon left> mdi-cloud-upload </v-icon>
|
<v-icon left> mdi-cloud-upload </v-icon>
|
||||||
{{ $t('general.upload') }}
|
{{ $t("general.upload") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-form>
|
</v-form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,7 +15,6 @@ export default {
|
||||||
url: String,
|
url: String,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
defaultButtonText: this.$t("general.upload"),
|
|
||||||
file: null,
|
file: null,
|
||||||
isSelecting: false,
|
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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<CategorySidebar/>
|
||||||
|
|
||||||
<CardSection
|
<CardSection
|
||||||
v-if="pageSettings.showRecent"
|
v-if="showRecent"
|
||||||
title="Recent"
|
title="Recent"
|
||||||
:recipes="recentRecipes"
|
:recipes="recentRecipes"
|
||||||
:card-limit="pageSettings.showLimit"
|
:card-limit="showLimit"
|
||||||
/>
|
/>
|
||||||
<CardSection
|
<CardSection
|
||||||
:sortable="true"
|
:sortable="true"
|
||||||
v-for="(section, index) in recipeByCategory"
|
v-for="(section, index) in recipeByCategory"
|
||||||
:key="index"
|
:key="index"
|
||||||
:title="section.title"
|
:title="section.name"
|
||||||
:recipes="section.recipes"
|
:recipes="section.recipes"
|
||||||
:card-limit="pageSettings.showLimit"
|
:card-limit="showLimit"
|
||||||
@sort="sortAZ(index)"
|
@sort="sortAZ(index)"
|
||||||
@sort-recent="sortRecent(index)"
|
@sort-recent="sortRecent(index)"
|
||||||
/>
|
/>
|
||||||
|
@ -20,34 +22,43 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import api from "../api";
|
||||||
import CardSection from "../components/UI/CardSection";
|
import CardSection from "../components/UI/CardSection";
|
||||||
|
import CategorySidebar from "../components/UI/CategorySidebar";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
CardSection,
|
CardSection,
|
||||||
|
CategorySidebar,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
recipeByCategory: [
|
recipeByCategory: [],
|
||||||
{
|
|
||||||
title: "Title 1",
|
|
||||||
recipes: this.$store.getters.getRecentRecipes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Title 2",
|
|
||||||
recipes: this.$store.getters.getRecentRecipes,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pageSettings() {
|
showRecent() {
|
||||||
return this.$store.getters.getHomePageSettings;
|
return this.$store.getters.getShowRecent;
|
||||||
|
},
|
||||||
|
showLimit() {
|
||||||
|
return this.$store.getters.getShowLimit;
|
||||||
|
},
|
||||||
|
homeCategories() {
|
||||||
|
return this.$store.getters.getHomeCategories;
|
||||||
},
|
},
|
||||||
recentRecipes() {
|
recentRecipes() {
|
||||||
return this.$store.getters.getRecentRecipes;
|
return this.$store.getters.getRecentRecipes;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.homeCategories.forEach(async (element) => {
|
||||||
|
let recipes = await this.getRecipeByCategory(element);
|
||||||
|
this.recipeByCategory.push(recipes);
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async getRecipeByCategory(category) {
|
||||||
|
return await api.categories.get_recipes_in_category(category);
|
||||||
|
},
|
||||||
getRecentRecipes() {
|
getRecentRecipes() {
|
||||||
this.$store.dispatch("requestRecentRecipes");
|
this.$store.dispatch("requestRecentRecipes");
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,8 @@ import SearchPage from "./pages/SearchPage";
|
||||||
import RecipePage from "./pages/RecipePage";
|
import RecipePage from "./pages/RecipePage";
|
||||||
import RecipeNewPage from "./pages/RecipeNewPage";
|
import RecipeNewPage from "./pages/RecipeNewPage";
|
||||||
import SettingsPage from "./pages/SettingsPage";
|
import SettingsPage from "./pages/SettingsPage";
|
||||||
|
import AllRecipesPage from "./pages/AllRecipesPage";
|
||||||
|
import CategoryPage from "./pages/CategoryPage";
|
||||||
import MeaplPlanPage from "./pages/MealPlanPage";
|
import MeaplPlanPage from "./pages/MealPlanPage";
|
||||||
import MealPlanThisWeekPage from "./pages/MealPlanThisWeekPage";
|
import MealPlanThisWeekPage from "./pages/MealPlanThisWeekPage";
|
||||||
import api from "./api";
|
import api from "./api";
|
||||||
|
@ -12,6 +14,8 @@ export const routes = [
|
||||||
{ path: "/", component: HomePage },
|
{ path: "/", component: HomePage },
|
||||||
{ path: "/mealie", component: HomePage },
|
{ path: "/mealie", component: HomePage },
|
||||||
{ path: "/search", component: SearchPage },
|
{ path: "/search", component: SearchPage },
|
||||||
|
{ path: "/recipes/all", component: AllRecipesPage },
|
||||||
|
{ path: "/recipes/:category", component: CategoryPage },
|
||||||
{ path: "/recipe/:recipe", component: RecipePage },
|
{ path: "/recipe/:recipe", component: RecipePage },
|
||||||
{ path: "/new/", component: RecipeNewPage },
|
{ path: "/new/", component: RecipeNewPage },
|
||||||
{ path: "/settings/site", component: SettingsPage },
|
{ 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 createPersistedState from "vuex-persistedstate";
|
||||||
import userSettings from "./modules/userSettings";
|
import userSettings from "./modules/userSettings";
|
||||||
import language from "./modules/language";
|
import language from "./modules/language";
|
||||||
|
import homePage from "./modules/homePage";
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
plugins: [
|
plugins: [
|
||||||
createPersistedState({
|
createPersistedState({
|
||||||
paths: ["userSettings", "language"],
|
paths: ["userSettings", "language", "homePage"],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
modules: {
|
modules: {
|
||||||
userSettings,
|
userSettings,
|
||||||
language,
|
language,
|
||||||
|
homePage,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
// Home Page Settings
|
// Home Page Settings
|
||||||
homePageSettings: {
|
|
||||||
showRecent: true,
|
|
||||||
showLimit: 9,
|
|
||||||
categories: [],
|
|
||||||
homeCategories: [],
|
|
||||||
},
|
|
||||||
// Snackbar
|
// Snackbar
|
||||||
snackActive: false,
|
snackActive: false,
|
||||||
snackText: "",
|
snackText: "",
|
||||||
|
@ -36,9 +32,6 @@ const store = new Vuex.Store({
|
||||||
},
|
},
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
setHomePageSettings(state, payload) {
|
|
||||||
state.homePageSettings = payload;
|
|
||||||
},
|
|
||||||
setSnackBar(state, payload) {
|
setSnackBar(state, payload) {
|
||||||
state.snackText = payload.text;
|
state.snackText = payload.text;
|
||||||
state.snackType = payload.type;
|
state.snackType = payload.type;
|
||||||
|
@ -67,26 +60,15 @@ const store = new Vuex.Store({
|
||||||
|
|
||||||
this.commit("setRecentRecipes", payload);
|
this.commit("setRecentRecipes", payload);
|
||||||
},
|
},
|
||||||
|
|
||||||
async requestHomePageSettings() {
|
|
||||||
// TODO: Query Backend for Categories
|
|
||||||
this.commit("setHomePageSettings", {
|
|
||||||
showRecent: true,
|
|
||||||
showLimit: 9,
|
|
||||||
categories: ["breakfast", "lunch", "dinner"],
|
|
||||||
homeCategories: [],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
//
|
//
|
||||||
getSnackText: state => state.snackText,
|
getSnackText: (state) => state.snackText,
|
||||||
getSnackActive: state => state.snackActive,
|
getSnackActive: (state) => state.snackActive,
|
||||||
getSnackType: state => state.snackType,
|
getSnackType: (state) => state.snackType,
|
||||||
|
|
||||||
getRecentRecipes: (state) => state.recentRecipes,
|
getRecentRecipes: (state) => state.recentRecipes,
|
||||||
getHomePageSettings: (state) => state.homePageSettings,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,12 @@ from utils.logger import logger
|
||||||
|
|
||||||
"""
|
"""
|
||||||
TODO:
|
TODO:
|
||||||
- [ ] Fix Duplicate Category
|
- [x] Fix Duplicate Category
|
||||||
- [ ] Fix Duplicate Tags
|
- [x] Fix Duplicate Tags
|
||||||
- [ ] Add Endpoint
|
- [ ] New Endpoints
|
||||||
|
- [x] Tag Endpoints
|
||||||
|
- [x] Category Endpoints
|
||||||
- [ ] Endpoint Tests
|
- [ ] Endpoint Tests
|
||||||
- [ ] Setup Database Migrations
|
|
||||||
- [ ] Finish Frontend Category Management
|
- [ ] Finish Frontend Category Management
|
||||||
- [ ] Ingredient Drag-Drop / Reorder
|
- [ ] Ingredient Drag-Drop / Reorder
|
||||||
- [ ] Refactor Endpoints
|
- [ ] Refactor Endpoints
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue