Meal planner improvements (#107)

* dev-bug: fixed vscode freezes

* test: refactor database init to support tests

* mealplan CRUD testing

* restructure test folder

* git attributes

* tests: migration, settings, theme routes testing

* docker-file shrink

* rebuild

* refactor: moving directories around

* adding funding

* mealplan redesign

Co-authored-by: Hayden <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-01-20 17:01:43 -09:00 committed by GitHub
commit 51893e89cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 174 deletions

View file

@ -1,10 +1,6 @@
<template>
<v-row>
<MealSelect
:forceDialog="dialog"
@close="dialog = false"
@select="setSlug($event)"
/>
<SearchDialog ref="mealselect" @select="setSlug" />
<v-col
cols="12"
sm="12"
@ -19,10 +15,10 @@
<v-img
height="200"
:src="getImage(meal.slug)"
@click="selectRecipe(index)"
@click="openSearch(index)"
></v-img>
<v-card-title class="my-n3 mb-n6">{{ meal.dateText }}</v-card-title>
<v-card-subtitle> {{ meal.slug }}</v-card-subtitle>
<v-card-subtitle> {{ meal.name }}</v-card-subtitle>
</v-card>
</v-hover>
</v-col>
@ -31,10 +27,10 @@
<script>
import utils from "../../utils";
import MealSelect from "./MealSelect";
import SearchDialog from "../UI/SearchDialog";
export default {
components: {
MealSelect,
SearchDialog,
},
props: {
value: Array,
@ -44,7 +40,6 @@ export default {
recipeData: [],
cardData: [],
activeIndex: 0,
dialog: false,
};
},
methods: {
@ -53,20 +48,14 @@ export default {
return utils.getImageURL(slug);
}
},
setSlug(slug) {
setSlug(name, slug) {
let index = this.activeIndex;
this.value[index]["slug"] = slug;
this.value[index]["name"] = name;
},
selectRecipe(index) {
openSearch(index) {
this.activeIndex = index;
this.dialog = true;
},
getProperty(index, property) {
try {
return this.recipeData[index][property];
} catch {
return null;
}
this.$refs.mealselect.open();
},
},
};

View file

@ -1,100 +0,0 @@
<template>
<v-row justify="center">
<v-dialog v-model="dialog" persistent max-width="800">
<v-card>
<v-card-title class="headline"> {{$t('meal-plan.choose-a-recipe')}} </v-card-title>
<v-card-text>
<v-autocomplete
:items="availableRecipes"
v-model="selected"
clearable
return
dense
hide-details
hide-selected
item-text="slug"
:label="$t('search.search-for-a-recipe')"
single-line
>
<template v-slot:no-data>
<v-list-item>
<v-list-item-title :v-html="$t('search.search-for-your-favorite-recipe')">
</v-list-item-title>
</v-list-item>
</template>
<template v-slot:item="{ item }">
<v-row align="center" @click="dialog = false">
<v-col sm="2">
<v-img
max-height="100"
max-width="100"
:src="getImage(item.image)"
></v-img>
</v-col>
<v-col sm="10">
<h3>
{{ item.name }}
</h3>
</v-col>
</v-row>
</template>
</v-autocomplete>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="secondary" text @click="dialog = false"> {{$t('general.close')}} </v-btn>
<v-btn color="secondary" text @click="dialog = false"> {{$t('general.select')}} </v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
<script>
import utils from "../../utils";
export default {
props: {
forceDialog: Boolean,
},
data() {
return {
dialog: false,
selected: "",
};
},
watch: {
forceDialog() {
this.dialog = this.forceDialog;
},
selected() {
if (this.selected) {
this.$emit("select", this.selected);
}
},
dialog() {
if (this.dialog === false) {
this.$emit("close");
} else {
this.selected = "";
}
},
},
computed: {
availableRecipes() {
return this.$store.getters.getRecentRecipes;
},
},
methods: {
getImage(slug) {
return utils.getImageURL(slug);
},
},
};
</script>
<style>
</style>

View file

@ -25,9 +25,9 @@
<v-col align="end">
<v-tooltip top color="secondary" max-width="400" open-delay="50">
<template v-slot:activator="{ on, attrs }">
<v-btn color="secondary" v-on="on" v-bind="attrs" text
>{{$t('recipe.description')}}</v-btn
>
<v-btn color="secondary" v-on="on" v-bind="attrs" text>{{
$t("recipe.description")
}}</v-btn>
</template>
<span>{{ description }}</span>
</v-tooltip>
@ -47,10 +47,15 @@ export default {
description: String,
rating: Number,
image: String,
route: {
default: true,
},
},
methods: {
moreInfo(recipeSlug) {
this.$router.push(`/recipe/${recipeSlug}`);
if (this.route) {
this.$router.push(`/recipe/${recipeSlug}`);
} else this.$emit("click");
},
getImage(image) {
return utils.getImageURL(image);

View file

@ -0,0 +1,76 @@
<template>
<div class="text-center">
<v-dialog v-model="dialog" min-height="700" max-width="1000">
<v-card min-height="725" height="100%">
<v-card-text>
<v-card-title></v-card-title>
<v-row justify="center">
<v-col cols="1"> </v-col>
<v-col>
<SearchBar @results="updateResults" :show-results="false" />
</v-col>
<v-col cols="2">
<v-btn icon>
<v-icon large> mdi-filter </v-icon>
</v-btn>
</v-col>
</v-row>
<v-row v-if="searchResults">
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="item in searchResults.slice(0, 10)"
:key="item.item.name"
>
<RecipeCard
:route="false"
:name="item.item.name"
:description="item.item.description"
:slug="item.item.slug"
:rating="item.item.rating"
:image="item.item.image"
@click="emitSelect(item.item.name, item.item.slug)"
/>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
</div>
</template>
<script>
import SearchBar from "../UI/SearchBar";
import RecipeCard from "../UI/RecipeCard";
export default {
components: {
SearchBar,
RecipeCard,
},
data() {
return {
searchResults: null,
dialog: false,
};
},
methods: {
updateResults(results) {
this.searchResults = results;
},
emitSelect(name, slug) {
console.log(name, slug);
this.$emit("select", name, slug);
this.dialog = false;
},
open() {
this.dialog = true;
},
},
};
</script>
<style>
</style>

View file

@ -5,66 +5,71 @@
:meal-plan="editMealPlan"
@updated="planUpdated"
/>
<NewMeal v-else @created="requestMeals" />
<NewMeal v-else @created="requestMeals" class="mb-5" />
<v-card class="my-1">
<v-card-title class="headline"> {{$t('meal-plan.meal-plans')}} </v-card-title>
<v-card class="my-2">
<v-card-title class="headline">
{{ $t("meal-plan.meal-plans") }}
</v-card-title>
<v-divider></v-divider>
<v-timeline align-top :dense="$vuetify.breakpoint.smAndDown">
<v-timeline-item
class="mx-4"
<v-row no-gutters>
<v-col
:sm="6"
:md="6"
:lg="4"
:xl="3"
v-for="(mealplan, i) in plannedMeals"
:key="i"
color="accent lighten-2"
icon="mdi-silverware-variant"
fill-dot
>
<v-card>
<v-card-title class="white--text secondary lighten-1">
<v-card class="ml-2 mt-2 mr-0">
<v-card-title>
{{ formatDate(mealplan.startDate) }} -
{{ formatDate(mealplan.endDate) }}
</v-card-title>
<v-card-text>
<v-row dense align="center">
<v-col></v-col>
<v-col
<v-list nav>
<v-list-item-group color="primary">
<v-list-item
v-for="(meal, index) in mealplan.meals"
:key="generateKey(meal.slug, index)"
@click="$router.push(`/recipe/${meal.slug}`)"
>
<v-img
class="rounded-lg info"
:src="getImage(meal.image)"
height="80"
width="80"
<v-list-item-avatar
color="primary"
class="headline font-weight-light white--text"
>
</v-img>
</v-col>
<v-col></v-col>
</v-row>
<v-row class="mt-2 ml-1">
<v-btn
color="accent lighten-2"
class="mx-0"
text
@click="editPlan(mealplan.uid)"
>
{{$t('general.edit')}}
</v-btn>
<v-btn
color="error lighten-2"
class="mx-2"
text
@click="deletePlan(mealplan.uid)"
>
{{$t('general.delete')}}
</v-btn>
</v-row>
</v-card-text>
<v-img :src="getImage(meal.image)"></v-img>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="meal.name"></v-list-item-title>
<v-list-item-subtitle v-text="meal.dateText">
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
<v-card-actions class="mt-n5">
<v-spacer></v-spacer>
<v-btn
color="accent lighten-2"
class="mx-0"
text
@click="editPlan(mealplan.uid)"
>
{{ $t("general.edit") }}
</v-btn>
<v-btn
color="error lighten-2"
class="mx-2"
text
@click="deletePlan(mealplan.uid)"
>
{{ $t("general.delete") }}
</v-btn>
</v-card-actions>
</v-card>
</v-timeline-item>
</v-timeline>
</v-col>
</v-row>
</v-card>
</div>
</template>

View file

@ -53,17 +53,17 @@ export default {
const dow = days[dateObject.getUTCDay()];
const month = months[dateObject.getUTCMonth()];
const day = dateObject.getUTCDate();
const year = dateObject.getFullYear();
// const year = dateObject.getFullYear();
return `${dow}, ${month} ${day}, ${year}`;
return `${dow}, ${month} ${day}`;
},
getDateAsTextAlt(dateObject) {
const dow = days[dateObject.getUTCDay()];
const month = monthsShort[dateObject.getUTCMonth()];
const day = dateObject.getUTCDate();
const year = dateObject.getFullYear();
// const year = dateObject.getFullYear();
return `${dow}, ${month} ${day}, ${year}`;
return `${dow}, ${month} ${day}`;
},
getDateAsPythonDate(dateObject) {
const month = dateObject.getMonth() + 1;

View file

@ -32,6 +32,7 @@ class Meal(BaseModel):
class MealData(BaseModel):
name: Optional[str]
slug: str
dateText: str