mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	style(frontend): 🎨 refactor/rewrite UI for meal-planner (#717)
* add new creation links * style(frontend): 🎨 refactor/rewrite UI for meal-planner * add random meal Co-authored-by: Hayden <hay-kot@pm.me>
This commit is contained in:
		| @@ -11,7 +11,7 @@ | |||||||
|       :bottom-links="isAdmin ? bottomLink : []" |       :bottom-links="isAdmin ? bottomLink : []" | ||||||
|       @input="sidebar = !sidebar" |       @input="sidebar = !sidebar" | ||||||
|     > |     > | ||||||
|       <v-menu offset-y nudge-bottom="5" open-on-hover close-delay="30" nudge-right="15"> |       <v-menu offset-y nudge-bottom="5" open-on-hover close-delay="50" nudge-right="15"> | ||||||
|         <template #activator="{ on, attrs }"> |         <template #activator="{ on, attrs }"> | ||||||
|           <v-btn rounded large class="ml-2 mt-3" v-bind="attrs" v-on="on"> |           <v-btn rounded large class="ml-2 mt-3" v-bind="attrs" v-on="on"> | ||||||
|             <v-icon left large color="primary"> |             <v-icon left large color="primary"> | ||||||
| @@ -20,7 +20,7 @@ | |||||||
|             {{ $t("general.create") }} |             {{ $t("general.create") }} | ||||||
|           </v-btn> |           </v-btn> | ||||||
|         </template> |         </template> | ||||||
|         <v-list> |         <v-list dense class="my-0 py-0"> | ||||||
|           <template v-for="(item, index) in createLinks"> |           <template v-for="(item, index) in createLinks"> | ||||||
|             <v-divider v-if="item.divider" :key="index" class="mx-2"></v-divider> |             <v-divider v-if="item.divider" :key="index" class="mx-2"></v-divider> | ||||||
|             <v-list-item v-else :key="item.title" :to="item.to" exact> |             <v-list-item v-else :key="item.title" :to="item.to" exact> | ||||||
| @@ -91,9 +91,7 @@ export default defineComponent({ | |||||||
|           to: "/recipe/create?tab=url", |           to: "/recipe/create?tab=url", | ||||||
|           restricted: true, |           restricted: true, | ||||||
|         }, |         }, | ||||||
|         { |         { divider: true }, | ||||||
|           divider: true, |  | ||||||
|         }, |  | ||||||
|         { |         { | ||||||
|           icon: this.$globals.icons.edit, |           icon: this.$globals.icons.edit, | ||||||
|           title: "Create", |           title: "Create", | ||||||
| @@ -101,16 +99,30 @@ export default defineComponent({ | |||||||
|           to: "/recipe/create?tab=new", |           to: "/recipe/create?tab=new", | ||||||
|           restricted: true, |           restricted: true, | ||||||
|         }, |         }, | ||||||
|         { |         { divider: true }, | ||||||
|           divider: true, |  | ||||||
|         }, |  | ||||||
|         { |         { | ||||||
|           icon: this.$globals.icons.zip, |           icon: this.$globals.icons.zip, | ||||||
|           title: "Restore", |           title: "Recipe from zip", | ||||||
|           subtitle: "Restore from a exported recipe", |           subtitle: "Restore from a exported recipe", | ||||||
|           to: "/recipe/create?tab=zip", |           to: "/recipe/create?tab=zip", | ||||||
|           restricted: true, |           restricted: true, | ||||||
|         }, |         }, | ||||||
|  |         { divider: true }, | ||||||
|  |         { | ||||||
|  |           icon: this.$globals.icons.pages, | ||||||
|  |           title: "Cookbook", | ||||||
|  |           subtitle: "Create a new cookbook", | ||||||
|  |           to: "/user/group/cookbooks", | ||||||
|  |           restricted: true, | ||||||
|  |         }, | ||||||
|  |         { divider: true }, | ||||||
|  |         { | ||||||
|  |           icon: this.$globals.icons.cartCheck, | ||||||
|  |           title: "Shopping List", | ||||||
|  |           subtitle: "Create a new shopping list", | ||||||
|  |           to: "/user/group/shopping-list/create", | ||||||
|  |           restricted: true, | ||||||
|  |         }, | ||||||
|       ], |       ], | ||||||
|       bottomLink: [ |       bottomLink: [ | ||||||
|         { |         { | ||||||
| @@ -138,12 +150,6 @@ export default defineComponent({ | |||||||
|               to: "/meal-plan/this-week", |               to: "/meal-plan/this-week", | ||||||
|               restricted: true, |               restricted: true, | ||||||
|             }, |             }, | ||||||
|             { |  | ||||||
|               icon: this.$globals.icons.calendarToday, |  | ||||||
|               title: this.$t("meal-plan.dinner-today"), |  | ||||||
|               to: "/meal-plan/today", |  | ||||||
|               restricted: true, |  | ||||||
|             }, |  | ||||||
|           ], |           ], | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -189,8 +189,7 @@ | |||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { defineComponent, ref, useContext } from "@nuxtjs/composition-api"; | import { defineComponent, ref, useContext, computed, reactive } from "@nuxtjs/composition-api"; | ||||||
| import { computed, reactive } from "@vue/reactivity"; |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   layout: "basic", |   layout: "basic", | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,16 @@ | |||||||
| <template> | <template> | ||||||
|   <v-container> |   <v-container> | ||||||
|     <v-card> |     <!-- Create Meal Dialog --> | ||||||
|       <v-card-title class="headline">New Recipe</v-card-title> |     <BaseDialog | ||||||
|  |       ref="domMealDialog" | ||||||
|  |       :title="$t('meal-plan.create-a-new-meal-plan')" | ||||||
|  |       color="primary" | ||||||
|  |       :icon="$globals.icons.foods" | ||||||
|  |       @submit=" | ||||||
|  |         actions.createOne(newMeal); | ||||||
|  |         resetDialog(); | ||||||
|  |       " | ||||||
|  |     > | ||||||
|       <v-card-text> |       <v-card-text> | ||||||
|         <v-menu |         <v-menu | ||||||
|           v-model="pickerMenu" |           v-model="pickerMenu" | ||||||
| @@ -25,8 +34,9 @@ | |||||||
|           </template> |           </template> | ||||||
|           <v-date-picker v-model="newMeal.date" no-title @input="pickerMenu = false"></v-date-picker> |           <v-date-picker v-model="newMeal.date" no-title @input="pickerMenu = false"></v-date-picker> | ||||||
|         </v-menu> |         </v-menu> | ||||||
|  |         <v-card-text> | ||||||
|           <v-autocomplete |           <v-autocomplete | ||||||
|           v-if="!noteOnly" |             v-if="!dialog.note" | ||||||
|             v-model="newMeal.recipeId" |             v-model="newMeal.recipeId" | ||||||
|             label="Meal Recipe" |             label="Meal Recipe" | ||||||
|             :items="allRecipes" |             :items="allRecipes" | ||||||
| @@ -36,29 +46,39 @@ | |||||||
|           ></v-autocomplete> |           ></v-autocomplete> | ||||||
|           <template v-else> |           <template v-else> | ||||||
|             <v-text-field v-model="newMeal.title" label="Meal Title"> </v-text-field> |             <v-text-field v-model="newMeal.title" label="Meal Title"> </v-text-field> | ||||||
|           <v-textarea v-model="newMeal.text" label="Meal Note"> </v-textarea> |             <v-textarea v-model="newMeal.text" rows="2" label="Meal Note"> </v-textarea> | ||||||
|           </template> |           </template> | ||||||
|         </v-card-text> |         </v-card-text> | ||||||
|       <v-card-actions> |         <v-card-actions class="my-0 py-0"> | ||||||
|         <v-switch v-model="noteOnly" label="Note Only"></v-switch> |           <v-switch v-model="dialog.note" class="mt-n3" label="Note Only"></v-switch> | ||||||
|         <v-spacer></v-spacer> |  | ||||||
|         <BaseButton @click="actions.createOne(newMeal)" /> |  | ||||||
|         </v-card-actions> |         </v-card-actions> | ||||||
|     </v-card> |       </v-card-text> | ||||||
|  |     </BaseDialog> | ||||||
|  |  | ||||||
|  |     <!-- Date Forward / Back --> | ||||||
|  |     <div class="d-flex justify-center flex-column"> | ||||||
|  |       <h3 class="text-h6 mt-2 text-center">{{ $d(weekRange.start, "short") }} - {{ $d(weekRange.end, "short") }}</h3> | ||||||
|       <div class="d-flex justify-center my-2 align-center" style="gap: 10px"> |       <div class="d-flex justify-center my-2 align-center" style="gap: 10px"> | ||||||
|       <v-btn icon color="info" rounded outlined @click="backOneWeek"> |         <v-btn icon color="info" outlined @click="backOneWeek"> | ||||||
|           <v-icon>{{ $globals.icons.back }} </v-icon> |           <v-icon>{{ $globals.icons.back }} </v-icon> | ||||||
|         </v-btn> |         </v-btn> | ||||||
|       <v-btn rounded outlined readonly style="pointer-events: none"> |         <v-btn icon color="info" outlined @click="forwardOneWeek"> | ||||||
|         {{ $d(weekRange.start, "short") }} - {{ $d(weekRange.end, "short") }} |  | ||||||
|       </v-btn> |  | ||||||
|       <v-btn icon color="info" rounded outlined @click="forwardOneWeek"> |  | ||||||
|           <v-icon>{{ $globals.icons.forward }} </v-icon> |           <v-icon>{{ $globals.icons.forward }} </v-icon> | ||||||
|         </v-btn> |         </v-btn> | ||||||
|       </div> |       </div> | ||||||
|  |     </div> | ||||||
|  |     <v-switch v-model="edit" label="Editor"></v-switch> | ||||||
|     <v-row class="mt-2"> |     <v-row class="mt-2"> | ||||||
|       <v-col v-for="(plan, index) in mealsByDate" :key="index" cols="12" sm="12" md="4" lg="3" xl="2"> |       <v-col | ||||||
|  |         v-for="(plan, index) in mealsByDate" | ||||||
|  |         :key="index" | ||||||
|  |         cols="12" | ||||||
|  |         sm="12" | ||||||
|  |         md="4" | ||||||
|  |         lg="3" | ||||||
|  |         xl="2" | ||||||
|  |         class="col-borders my-1 d-flex flex-column" | ||||||
|  |       > | ||||||
|         <p class="h5 text-center"> |         <p class="h5 text-center"> | ||||||
|           {{ $d(plan.date, "short") }} |           {{ $d(plan.date, "short") }} | ||||||
|         </p> |         </p> | ||||||
| @@ -71,9 +91,14 @@ | |||||||
|           style="min-height: 150px" |           style="min-height: 150px" | ||||||
|           @end="onMoveCallback" |           @end="onMoveCallback" | ||||||
|         > |         > | ||||||
|           <v-hover v-for="mealplan in plan.meals" :key="mealplan.id" v-model="hover[mealplan.id]" open-delay="100"> |           <v-card v-for="mealplan in plan.meals" :key="mealplan.id" v-model="hover[mealplan.id]" class="my-1"> | ||||||
|             <v-card class="my-2"> |  | ||||||
|             <v-list-item> |             <v-list-item> | ||||||
|  |               <v-list-item-avatar :rounded="false"> | ||||||
|  |                 <RecipeCardImage v-if="mealplan.recipe" tiny icon-size="25" :slug="mealplan.recipe.slug" /> | ||||||
|  |                 <v-icon v-else> | ||||||
|  |                   {{ $globals.icons.primary }} | ||||||
|  |                 </v-icon> | ||||||
|  |               </v-list-item-avatar> | ||||||
|               <v-list-item-content> |               <v-list-item-content> | ||||||
|                 <v-list-item-title class="mb-1"> |                 <v-list-item-title class="mb-1"> | ||||||
|                   {{ mealplan.recipe ? mealplan.recipe.name : mealplan.title }} |                   {{ mealplan.recipe ? mealplan.recipe.name : mealplan.title }} | ||||||
| @@ -83,34 +108,66 @@ | |||||||
|                 </v-list-item-subtitle> |                 </v-list-item-subtitle> | ||||||
|               </v-list-item-content> |               </v-list-item-content> | ||||||
|             </v-list-item> |             </v-list-item> | ||||||
|  |             <v-divider class="mx-2"></v-divider> | ||||||
|  |             <v-card-actions> | ||||||
|  |               <v-btn color="error" icon @click="actions.deleteOne(mealplan.id)"> | ||||||
|  |                 <v-icon>{{ $globals.icons.delete }}</v-icon> | ||||||
|  |               </v-btn> | ||||||
|  |               <v-spacer></v-spacer> | ||||||
|  |               <v-btn | ||||||
|  |                 v-if="mealplan.recipe" | ||||||
|  |                 color="info" | ||||||
|  |                 icon | ||||||
|  |                 nuxt | ||||||
|  |                 target="_blank" | ||||||
|  |                 :to="`/recipe/${mealplan.recipe.slug}`" | ||||||
|  |               > | ||||||
|  |                 <v-icon>{{ $globals.icons.openInNew }}</v-icon> | ||||||
|  |               </v-btn> | ||||||
|  |             </v-card-actions> | ||||||
|           </v-card> |           </v-card> | ||||||
|           </v-hover> |  | ||||||
|         </draggable> |         </draggable> | ||||||
|  |         <v-card v-if="edit" 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> | ||||||
|       </v-col> |       </v-col> | ||||||
|     </v-row> |     </v-row> | ||||||
|   </v-container> |   </v-container> | ||||||
| </template> | </template> | ||||||
|    |    | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api"; | import { computed, defineComponent, reactive, ref, toRefs, watch } from "@nuxtjs/composition-api"; | ||||||
| import { isSameDay, addDays, subDays, parseISO, format } from "date-fns"; | import { isSameDay, addDays, subDays, parseISO, format } from "date-fns"; | ||||||
| import { SortableEvent } from "sortablejs"; // eslint-disable-line | import { SortableEvent } from "sortablejs"; // eslint-disable-line | ||||||
| import draggable from "vuedraggable"; | import draggable from "vuedraggable"; | ||||||
| import { useMealplans } from "~/composables/use-group-mealplan"; | import { useMealplans } from "~/composables/use-group-mealplan"; | ||||||
| import { useRecipes, allRecipes } from "~/composables/use-recipes"; | import { useRecipes, allRecipes } from "~/composables/use-recipes"; | ||||||
|  | import RecipeCardImage from "~/components/Domain/Recipe/RecipeCardImage.vue"; | ||||||
|  |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   components: { |   components: { | ||||||
|     draggable, |     draggable, | ||||||
|  |     RecipeCardImage, | ||||||
|   }, |   }, | ||||||
|   setup() { |   setup() { | ||||||
|     const { mealplans, actions } = useMealplans(); |     const { mealplans, actions } = useMealplans(); | ||||||
|  |  | ||||||
|     useRecipes(true, true); |     useRecipes(true, true); | ||||||
|     const state = reactive({ |     const state = reactive({ | ||||||
|  |       edit: false, | ||||||
|       hover: {}, |       hover: {}, | ||||||
|       pickerMenu: null, |       pickerMenu: null, | ||||||
|       noteOnly: false, |  | ||||||
|       start: null as Date | null, |       start: null as Date | null, | ||||||
|       today: new Date(), |       today: new Date(), | ||||||
|       end: null as Date | null, |       end: null as Date | null, | ||||||
| @@ -169,9 +226,9 @@ export default defineComponent({ | |||||||
|  |  | ||||||
|     const weekRange = computed(() => { |     const weekRange = computed(() => { | ||||||
|       // @ts-ignore - Not Sure Why This is not working |       // @ts-ignore - Not Sure Why This is not working | ||||||
|       const end = addDays(state.today, 2); |       const end = addDays(state.today, 6); | ||||||
|       // @ts-ignore - Not sure why the type is invalid |       // @ts-ignore - Not sure why the type is invalid | ||||||
|       const start = subDays(state.today, 2); |       const start = subDays(state.today, 1); | ||||||
|       return { start, end, today: state.today }; |       return { start, end, today: state.today }; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -183,14 +240,62 @@ export default defineComponent({ | |||||||
|       ); |       ); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     // ===================================================== | ||||||
|  |     // New Meal Dialog | ||||||
|  |     const domMealDialog = ref(null); | ||||||
|  |     const dialog = reactive({ | ||||||
|  |       loading: false, | ||||||
|  |       error: false, | ||||||
|  |       note: false, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     watch(dialog, () => { | ||||||
|  |       if (dialog.note) { | ||||||
|  |         newMeal.recipeId = null; | ||||||
|  |       } | ||||||
|  |       newMeal.title = ""; | ||||||
|  |       newMeal.text = ""; | ||||||
|  |     }); | ||||||
|     const newMeal = reactive({ |     const newMeal = reactive({ | ||||||
|       date: null, |       date: "", | ||||||
|       title: "", |       title: "", | ||||||
|       text: "", |       text: "", | ||||||
|       recipeId: null, |       recipeId: null, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     function openDialog(date: Date) { | ||||||
|  |       newMeal.date = format(date, "yyyy-MM-dd"); | ||||||
|  |       // @ts-ignore | ||||||
|  |       domMealDialog.value.open(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function resetDialog() { | ||||||
|  |       newMeal.date = ""; | ||||||
|  |       newMeal.title = ""; | ||||||
|  |       newMeal.text = ""; | ||||||
|  |       newMeal.recipeId = null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function randomMeal(date: Date) { | ||||||
|  |       // TODO: Refactor to use API call to get random recipe | ||||||
|  |       // @ts-ignore | ||||||
|  |       const randomRecipe = allRecipes.value[Math.floor(Math.random() * allRecipes.value.length)]; | ||||||
|  |  | ||||||
|  |       newMeal.date = format(date, "yyyy-MM-dd"); | ||||||
|  |       // @ts-ignore | ||||||
|  |       newMeal.recipeId = randomRecipe.id; | ||||||
|  |  | ||||||
|  |       // @ts-ignore | ||||||
|  |       actions.createOne(newMeal); | ||||||
|  |       resetDialog(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|  |       resetDialog, | ||||||
|  |       randomMeal, | ||||||
|  |       dialog, | ||||||
|  |       domMealDialog, | ||||||
|  |       openDialog, | ||||||
|       mealplans, |       mealplans, | ||||||
|       actions, |       actions, | ||||||
|       newMeal, |       newMeal, | ||||||
| @@ -207,3 +312,9 @@ export default defineComponent({ | |||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | <style lang="css"> | ||||||
|  | .col-borders { | ||||||
|  |   border-top: 1px solid #e0e0e0; | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  |    | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| <template> | <template> | ||||||
|   <div></div> |   <div>This Week</div> | ||||||
| </template> | </template> | ||||||
|    |    | ||||||
|   <script lang="ts"> |   <script lang="ts"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user