mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -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:
parent
bd8882ec44
commit
f9829141c0
@ -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,40 +34,51 @@
|
|||||||
</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-autocomplete
|
<v-card-text>
|
||||||
v-if="!noteOnly"
|
<v-autocomplete
|
||||||
v-model="newMeal.recipeId"
|
v-if="!dialog.note"
|
||||||
label="Meal Recipe"
|
v-model="newMeal.recipeId"
|
||||||
:items="allRecipes"
|
label="Meal Recipe"
|
||||||
item-text="name"
|
:items="allRecipes"
|
||||||
item-value="id"
|
item-text="name"
|
||||||
:return-object="false"
|
item-value="id"
|
||||||
></v-autocomplete>
|
:return-object="false"
|
||||||
<template v-else>
|
></v-autocomplete>
|
||||||
<v-text-field v-model="newMeal.title" label="Meal Title"> </v-text-field>
|
<template v-else>
|
||||||
<v-textarea v-model="newMeal.text" label="Meal Note"> </v-textarea>
|
<v-text-field v-model="newMeal.title" label="Meal Title"> </v-text-field>
|
||||||
</template>
|
<v-textarea v-model="newMeal.text" rows="2" label="Meal Note"> </v-textarea>
|
||||||
|
</template>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="my-0 py-0">
|
||||||
|
<v-switch v-model="dialog.note" class="mt-n3" label="Note Only"></v-switch>
|
||||||
|
</v-card-actions>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
</BaseDialog>
|
||||||
<v-switch v-model="noteOnly" label="Note Only"></v-switch>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<BaseButton @click="actions.createOne(newMeal)" />
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
|
|
||||||
<div class="d-flex justify-center my-2 align-center" style="gap: 10px">
|
<!-- Date Forward / Back -->
|
||||||
<v-btn icon color="info" rounded outlined @click="backOneWeek">
|
<div class="d-flex justify-center flex-column">
|
||||||
<v-icon>{{ $globals.icons.back }} </v-icon>
|
<h3 class="text-h6 mt-2 text-center">{{ $d(weekRange.start, "short") }} - {{ $d(weekRange.end, "short") }}</h3>
|
||||||
</v-btn>
|
<div class="d-flex justify-center my-2 align-center" style="gap: 10px">
|
||||||
<v-btn rounded outlined readonly style="pointer-events: none">
|
<v-btn icon color="info" outlined @click="backOneWeek">
|
||||||
{{ $d(weekRange.start, "short") }} - {{ $d(weekRange.end, "short") }}
|
<v-icon>{{ $globals.icons.back }} </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon color="info" rounded outlined @click="forwardOneWeek">
|
<v-btn icon color="info" 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,46 +91,83 @@
|
|||||||
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">
|
||||||
<v-list-item-content>
|
<RecipeCardImage v-if="mealplan.recipe" tiny icon-size="25" :slug="mealplan.recipe.slug" />
|
||||||
<v-list-item-title class="mb-1">
|
<v-icon v-else>
|
||||||
{{ mealplan.recipe ? mealplan.recipe.name : mealplan.title }}
|
{{ $globals.icons.primary }}
|
||||||
</v-list-item-title>
|
</v-icon>
|
||||||
<v-list-item-subtitle>
|
</v-list-item-avatar>
|
||||||
{{ mealplan.recipe ? mealplan.recipe.description : mealplan.text }}
|
<v-list-item-content>
|
||||||
</v-list-item-subtitle>
|
<v-list-item-title class="mb-1">
|
||||||
</v-list-item-content>
|
{{ mealplan.recipe ? mealplan.recipe.name : mealplan.title }}
|
||||||
</v-list-item>
|
</v-list-item-title>
|
||||||
</v-card>
|
<v-list-item-subtitle>
|
||||||
</v-hover>
|
{{ mealplan.recipe ? mealplan.recipe.description : mealplan.text }}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</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>
|
||||||
</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">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user