Refactor #4
This commit is contained in:
parent
a6904524db
commit
8797a5489c
4 changed files with 165 additions and 139 deletions
|
|
@ -41,6 +41,7 @@ Outcome: Components get smaller and easier to read; business logic is reusable.
|
||||||
- [x] Add Prettier config and .editorconfig; wire Prettier with ESLint
|
- [x] Add Prettier config and .editorconfig; wire Prettier with ESLint
|
||||||
- [ ] Upgrade ESLint (if/when convenient) and align with Vue 3 rules
|
- [ ] Upgrade ESLint (if/when convenient) and align with Vue 3 rules
|
||||||
- [ ] Ensure Volar is used (dev environment) for Vue 3 type intelligence
|
- [ ] Ensure Volar is used (dev environment) for Vue 3 type intelligence
|
||||||
|
- Optional next: add lint-staged + husky for `pre-commit` formatting
|
||||||
|
|
||||||
Outcome: Stable formatting and consistent linting across contributors.
|
Outcome: Stable formatting and consistent linting across contributors.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,16 +52,17 @@
|
||||||
<editable-ingredients-panel :ingredients="meal.extra_ingredients" @on-add="addIngredient" @on-delete="deleteIngredient" @on-update-ingredient="updateIngredient" @on-editing="onEditAdditionalIngredients" />
|
<editable-ingredients-panel :ingredients="meal.extra_ingredients" @on-add="addIngredient" @on-delete="deleteIngredient" @on-update-ingredient="updateIngredient" @on-editing="onEditAdditionalIngredients" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button @click="saveMeal">Save</button>
|
<button @click="onSaveMeal">Save</button>
|
||||||
<p v-if="meal.purchase_date"><em>Purchased {{ ago(meal.purchase_date) }}</em></p>
|
<p v-if="meal.purchase_date"><em>Purchased {{ ago(meal.purchase_date) }}</em></p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { reactive, onBeforeMount } from 'vue'
|
||||||
import { getMeal, saveMeal } from '@/api/meals';
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { getRecipe } from '@/api/recipes';
|
import { getMeal, saveMeal } from '@/composables/useMeals'
|
||||||
import { currentUser } from '@/api/auth';
|
import { getRecipe } from '@/api/recipes'
|
||||||
|
import { currentUser } from '@/api/auth'
|
||||||
import alert from '@/alert.js';
|
import alert from '@/alert.js';
|
||||||
|
|
||||||
import { ago } from '@/dateformats.js';
|
import { ago } from '@/dateformats.js';
|
||||||
|
|
@ -79,110 +80,114 @@ function addPersonIfNotExists(list, person) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
const route = useRoute()
|
||||||
props: ['id'],
|
const router = useRouter()
|
||||||
components: { RecipeSearchBox, DatePicker, RecipeCard, EditableIngredientsPanel, PersonList, CompactParsedIngredient },
|
|
||||||
data() {
|
const showIngredients = reactive({})
|
||||||
return {
|
const meal = reactive({
|
||||||
showIngredients: {},
|
|
||||||
meal: {
|
|
||||||
id: -1,
|
id: -1,
|
||||||
suggested_date: new Date(),
|
suggested_date: new Date(),
|
||||||
recipes: [],
|
recipes: [],
|
||||||
extra_ingredients: [],
|
extra_ingredients: [],
|
||||||
chefs: [],
|
chefs: [],
|
||||||
consumers: [],
|
consumers: [],
|
||||||
cleanup: []
|
cleanup: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
const idParam = route.params.id
|
||||||
|
const id = typeof idParam === 'string' ? parseInt(idParam) : idParam
|
||||||
|
if (id >= 0) {
|
||||||
|
const loaded = await getMeal(id)
|
||||||
|
Object.assign(meal, loaded)
|
||||||
|
} else {
|
||||||
|
const self = await currentUser()
|
||||||
|
Object.assign(meal, { chefs: [self], consumers: [self], cleanup: [self] })
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
},
|
|
||||||
async beforeMount() {
|
function selectDate(date) {
|
||||||
if (this.id >= 0) {
|
meal.suggested_date = date
|
||||||
this.meal = await getMeal(this.id);
|
}
|
||||||
|
|
||||||
|
function removeRecipe(mealRecipe) {
|
||||||
|
if (confirm(`Are you sure you want to remove ${mealRecipe.servings} servings of ${mealRecipe.recipe.name} from this meal?`)) {
|
||||||
|
meal.recipes = meal.recipes.filter((r) => r != mealRecipe)
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
const self = await currentUser();
|
|
||||||
this.meal = {...this.meal, chefs: [self], consumers: [self], cleanup: [self], };
|
function addIngredient() {
|
||||||
}
|
meal.extra_ingredients = [{ line: '', product: null }, ...meal.extra_ingredients]
|
||||||
},
|
}
|
||||||
methods: {
|
|
||||||
ago,
|
function deleteIngredient(ingredient) {
|
||||||
async selectRecipe(recipe) {
|
meal.extra_ingredients = meal.extra_ingredients.filter((i) => i != ingredient)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateIngredient(ingredient, newIngredient) {
|
||||||
|
meal.extra_ingredients = meal.extra_ingredients.map((i) => (i == ingredient ? newIngredient : i))
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePerson(list, person) {
|
||||||
|
meal[list] = meal[list].filter((p) => p.id !== person.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPerson(list, person) {
|
||||||
|
addPersonIfNotExists(meal[list], person)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectRecipe(recipe) {
|
||||||
// Refetch to get additional details
|
// Refetch to get additional details
|
||||||
recipe = await getRecipe(recipe.id);
|
recipe = await getRecipe(recipe.id)
|
||||||
|
|
||||||
if (recipe.created_by) {
|
if (recipe.created_by) {
|
||||||
addPersonIfNotExists(this.meal.chefs, recipe.created_by);
|
addPersonIfNotExists(meal.chefs, recipe.created_by)
|
||||||
addPersonIfNotExists(this.meal.consumers, recipe.created_by);
|
addPersonIfNotExists(meal.consumers, recipe.created_by)
|
||||||
|
|
||||||
if (this.meal.cleanup.length === 0) {
|
if (meal.cleanup.length === 0) {
|
||||||
addPersonIfNotExists(this.meal.cleanup, recipe.created_by);
|
addPersonIfNotExists(meal.cleanup, recipe.created_by)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.meal.recipes.push({ recipe, recipe_id: recipe.id, meal_id: this.meal.id, servings: recipe.serves });
|
meal.recipes.push({ recipe, recipe_id: recipe.id, meal_id: meal.id, servings: recipe.serves })
|
||||||
},
|
}
|
||||||
selectDate(date) {
|
|
||||||
this.meal.suggested_date = date;
|
|
||||||
},
|
|
||||||
removeRecipe(mealRecipe) {
|
|
||||||
if (confirm(`Are you sure you want to remove ${mealRecipe.servings} servings of ${mealRecipe.recipe.name} from this meal?`)) {
|
|
||||||
this.meal.recipes = this.meal.recipes.filter(r => r != mealRecipe);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addIngredient() {
|
|
||||||
this.meal.extra_ingredients = [{ line: '', product: null }, ...this.meal.extra_ingredients];
|
|
||||||
},
|
|
||||||
deleteIngredient(ingredient) {
|
|
||||||
this.meal.extra_ingredients = this.meal.extra_ingredients.filter(i => i != ingredient);
|
|
||||||
},
|
|
||||||
updateIngredient(ingredient, newIngredient) {
|
|
||||||
this.meal.extra_ingredients = this.meal.extra_ingredients.map(i => i == ingredient ? newIngredient : i);
|
|
||||||
},
|
|
||||||
removePerson(list, person) {
|
|
||||||
this.meal[list] = this.meal[list].filter(p => p.id !== person.id);
|
|
||||||
},
|
|
||||||
addPerson(list, person) {
|
|
||||||
addPersonIfNotExists(this.meal[list], person);
|
|
||||||
},
|
|
||||||
async saveMeal() {
|
|
||||||
const meal = await saveMeal(this.meal);
|
|
||||||
if (meal?.id >= 0) {
|
|
||||||
this.meal = meal;
|
|
||||||
|
|
||||||
this.$router.push(`/meals/${meal.id}`);
|
async function onEditAdditionalIngredients(editing) {
|
||||||
alert.show({ heading: 'Meal saved', message: 'Meal saved successfully', type: 'success' });
|
if (editing && meal.extra_ingredients.length === 0) {
|
||||||
return;
|
addIngredient()
|
||||||
|
} else {
|
||||||
|
meal.extra_ingredients = meal.extra_ingredients.filter((i) => i.line)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
alert.show({ heading: 'Error saving meal', message: 'An error occurred while saving the meal', type: 'error' });
|
function showIngredient(mealRecipe, value) {
|
||||||
},
|
const index = meal.recipes.indexOf(mealRecipe)
|
||||||
onEditAdditionalIngredients(editing) {
|
const key = `${mealRecipe.recipe.id}-${index}`
|
||||||
if (editing && this.meal.extra_ingredients.length === 0) {
|
|
||||||
this.addIngredient();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.meal.extra_ingredients = this.meal.extra_ingredients.filter(i => i.line);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showIngredient(mealRecipe, value) {
|
|
||||||
const index = this.meal.recipes.indexOf(mealRecipe);
|
|
||||||
const key = `${mealRecipe.recipe.id}-${index}`;
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return this.showIngredients[key];
|
return showIngredients[key]
|
||||||
}
|
}
|
||||||
|
return (showIngredients[key] = value)
|
||||||
|
}
|
||||||
|
|
||||||
return this.showIngredients[key] = value;
|
function scaleIngredients(mealRecipe) {
|
||||||
},
|
return mealRecipe.recipe.ingredients.map((i) => {
|
||||||
scaleIngredients(mealRecipe) {
|
|
||||||
return mealRecipe.recipe.ingredients.map(i => {
|
|
||||||
return {
|
return {
|
||||||
...i,
|
...i,
|
||||||
quantity: i.quantity * mealRecipe.servings / mealRecipe.recipe.serves
|
quantity: (i.quantity * mealRecipe.servings) / mealRecipe.recipe.serves,
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSaveMeal() {
|
||||||
|
const saved = await saveMeal(meal)
|
||||||
|
if (saved?.id >= 0) {
|
||||||
|
Object.assign(meal, saved)
|
||||||
|
router.push(`/meals/${saved.id}`)
|
||||||
|
alert.show({ heading: 'Meal saved', message: 'Meal saved successfully', type: 'success' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
alert.show({ heading: 'Error saving meal', message: 'An error occurred while saving the meal', type: 'error' })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,41 +78,36 @@ ul.actions {
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
|
import { ref, onBeforeMount } from 'vue'
|
||||||
import ActionItem from '@/components/ActionItem.vue'
|
import ActionItem from '@/components/ActionItem.vue'
|
||||||
import MealCard from '@/components/meals/MealCard.vue'
|
import MealCard from '@/components/meals/MealCard.vue'
|
||||||
import { getUpcomingMeals, markMealConsumed, deleteMeal } from '@/api/meals'
|
import { getUpcomingMeals, markMealConsumed, deleteMeal } from '@/composables/useMeals'
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MealPlanPage',
|
|
||||||
components: { MealCard, ActionItem },
|
|
||||||
data() {
|
|
||||||
const from = new Date();
|
|
||||||
from.setTime(0);
|
|
||||||
|
|
||||||
const to = new Date();
|
const from = new Date()
|
||||||
to.setDate(to.getDate() + 7);
|
from.setTime(0)
|
||||||
|
|
||||||
return {
|
const to = new Date()
|
||||||
from, to,
|
to.setDate(to.getDate() + 7)
|
||||||
meals: [],
|
|
||||||
selectedMeal: null
|
const meals = ref([])
|
||||||
}
|
const selectedMeal = ref(null)
|
||||||
},
|
|
||||||
async beforeMount() {
|
onBeforeMount(async () => {
|
||||||
const meals = await getUpcomingMeals(this.from, this.to)
|
meals.value = await getUpcomingMeals(from, to)
|
||||||
this.meals = meals
|
})
|
||||||
},
|
|
||||||
methods: {
|
async function deleteSelectedMeal() {
|
||||||
async deleteSelectedMeal() {
|
if (!selectedMeal.value) return
|
||||||
await deleteMeal(this.selectedMeal.id)
|
await deleteMeal(selectedMeal.value.id)
|
||||||
this.meals = this.meals.filter(m => m.id !== this.selectedMeal.id)
|
meals.value = meals.value.filter((m) => m.id !== selectedMeal.value.id)
|
||||||
this.selectedMeal = null
|
selectedMeal.value = null
|
||||||
},
|
}
|
||||||
async markConsumed() {
|
|
||||||
await markMealConsumed(this.selectedMeal.id)
|
async function markConsumed() {
|
||||||
this.meals = this.meals.filter(m => m.id !== this.selectedMeal.id)
|
if (!selectedMeal.value) return
|
||||||
}
|
await markMealConsumed(selectedMeal.value.id)
|
||||||
}
|
meals.value = meals.value.filter((m) => m.id !== selectedMeal.value.id)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
25
src/composables/useMeals.js
Normal file
25
src/composables/useMeals.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as api from '@/api/meals'
|
||||||
|
|
||||||
|
export async function getUpcomingMeals(from, to) {
|
||||||
|
return api.getUpcomingMeals(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMeal(id) {
|
||||||
|
return api.getMeal(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveMeal(meal) {
|
||||||
|
return api.saveMeal(meal)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function markMealConsumed(mealId) {
|
||||||
|
return api.markMealConsumed(mealId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteMeal(mealId) {
|
||||||
|
return api.deleteMeal(mealId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMeals() {
|
||||||
|
return { getUpcomingMeals, getMeal, saveMeal, markMealConsumed, deleteMeal }
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue