Composition #2
This commit is contained in:
parent
61ca41d25a
commit
beab02200c
4 changed files with 129 additions and 150 deletions
|
|
@ -71,35 +71,31 @@ Last updated: 2025-10-18
|
|||
## Current status
|
||||
|
||||
Already using `<script setup>`:
|
||||
- Meals: `MealPlanPage.vue`, `EditMealPage.vue`
|
||||
- Shopping: `CurrentShoppingListPage.vue`, `MyShoppingPage.vue`, `PurchasedShoppingListPage.vue`
|
||||
- Meals: `MealPlanPage.vue`, `EditMealPage.vue`, `meals/MealCard.vue`
|
||||
- Shopping: `CurrentShoppingListPage.vue`, `MyShoppingPage.vue`, `PurchasedShoppingListPage.vue`, `shopping/MealSelectionList.vue`
|
||||
- Core/Leaf: `components/ActionItem.vue`, `recipes/RecipeCard.vue`, `ingredients/CompactParsedIngredient.vue`
|
||||
- Ingredients: `ingredients/IngredientLine.vue`, `ingredients/EditableIngredientsPanel.vue`
|
||||
|
||||
Remaining to migrate (Options API or mixed):
|
||||
|
||||
- Core
|
||||
- `App.vue`
|
||||
- `components/AlertToast.vue`
|
||||
- `components/ActionItem.vue`
|
||||
- `components/LoginPage.vue`
|
||||
|
||||
- Recipes
|
||||
- `components/recipes/RecipesPage.vue`
|
||||
- `components/recipes/RecipeSearchBox.vue`
|
||||
- `components/recipes/RecipeCard.vue`
|
||||
- `components/recipes/EditRecipePage.vue` (most complex)
|
||||
|
||||
- Meals
|
||||
- `components/meals/MealCard.vue`
|
||||
- `components/meals/DatePicker.vue`
|
||||
- `components/meals/PersonList.vue` (mixed Options+setup, unify under `<script setup>`)
|
||||
|
||||
- Ingredients
|
||||
- `components/ingredients/CompactParsedIngredient.vue`
|
||||
- `components/ingredients/IngredientLine.vue`
|
||||
- `components/ingredients/EditableIngredientsPanel.vue`
|
||||
|
||||
- Shopping
|
||||
- `components/shopping/MealSelectionList.vue`
|
||||
- `components/shopping/ShoppingListItem.vue`
|
||||
|
||||
## Migration order (batches)
|
||||
|
|
@ -144,20 +140,24 @@ Remaining to migrate (Options API or mixed):
|
|||
|
||||
- [ ] Core: `App.vue`
|
||||
- [ ] Core: `components/AlertToast.vue`
|
||||
- [ ] Core: `components/ActionItem.vue`
|
||||
- [x] Core: `components/ActionItem.vue`
|
||||
- [ ] Core: `components/LoginPage.vue`
|
||||
|
||||
- [ ] Recipes: `components/recipes/RecipesPage.vue`
|
||||
- [ ] Recipes: `components/recipes/RecipeSearchBox.vue`
|
||||
- [ ] Recipes: `components/recipes/RecipeCard.vue`
|
||||
- [x] Recipes: `components/recipes/RecipeCard.vue`
|
||||
- [ ] Recipes: `components/recipes/EditRecipePage.vue`
|
||||
|
||||
- [ ] Meals: `components/meals/MealCard.vue`
|
||||
- [ ] Meals: `components/meals/DatePicker.vue`
|
||||
- [ ] Meals: `components/meals/PersonList.vue`
|
||||
- [x] Meals: `components/meals/MealCard.vue`
|
||||
- [x] Meals: `components/meals/DatePicker.vue`
|
||||
- [x] Meals: `components/meals/PersonList.vue`
|
||||
|
||||
- [x] Ingredients: `components/ingredients/CompactParsedIngredient.vue`
|
||||
- [x] Ingredients: `components/ingredients/IngredientLine.vue`
|
||||
- [x] Ingredients: `components/ingredients/EditableIngredientsPanel.vue`
|
||||
|
||||
|
||||
- [ ] Shopping: `components/shopping/MealSelectionList.vue`
|
||||
- [x] Shopping: `components/shopping/MealSelectionList.vue`
|
||||
- [ ] Shopping: `components/shopping/ShoppingListItem.vue`
|
||||
|
||||
## Notes and risks
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div :class="{ editing: editing }">
|
||||
<button v-if="editing" @click="$emit('on-add')">
|
||||
<button v-if="editing" @click="emit('on-add')">
|
||||
<img class="icon" :src="require('@/assets/add-cart.svg')" /> <br />
|
||||
Add Ingredient
|
||||
</button>
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
@update-product-link="updateProduct"
|
||||
/>
|
||||
</p>
|
||||
<button @click="$emit('on-delete', ingredient)">
|
||||
<button @click="emit('on-delete', ingredient)">
|
||||
<img class="icon" :src="require('@/assets/trash.svg')" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -70,33 +70,32 @@ li > div {
|
|||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { parseProduct, parseIngredients } from '@/api/recipes'
|
||||
import IngredientLine from './IngredientLine.vue'
|
||||
import CompactParsedIngredient from './CompactParsedIngredient.vue'
|
||||
|
||||
export default {
|
||||
name: 'EditableIngredientsPanel',
|
||||
components: { IngredientLine, CompactParsedIngredient },
|
||||
props: ['ingredients', 'editOnly'],
|
||||
data() {
|
||||
return {
|
||||
editing: this.editOnly ?? false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updateProduct(ingredient, product_link) {
|
||||
const props = defineProps({
|
||||
ingredients: { type: Array, required: true },
|
||||
editOnly: { type: Boolean, default: false },
|
||||
})
|
||||
const emit = defineEmits(['on-add', 'on-delete', 'on-update-ingredient', 'on-editing'])
|
||||
|
||||
const editing = ref(props.editOnly ?? false)
|
||||
|
||||
async function updateProduct(ingredient, product_link) {
|
||||
const product = await parseProduct(ingredient, product_link)
|
||||
this.$emit('on-update-ingredient', ingredient, { ...ingredient, product })
|
||||
},
|
||||
async updateIngredient(ingredient, line) {
|
||||
emit('on-update-ingredient', ingredient, { ...ingredient, product })
|
||||
}
|
||||
|
||||
async function updateIngredient(ingredient, line) {
|
||||
const newIngredients = await parseIngredients([line])
|
||||
this.$emit('on-update-ingredient', ingredient, newIngredients[0])
|
||||
},
|
||||
toggleEditing() {
|
||||
this.editing = !this.editing
|
||||
this.$emit('on-editing', this.editing)
|
||||
},
|
||||
},
|
||||
emit('on-update-ingredient', ingredient, newIngredients[0])
|
||||
}
|
||||
|
||||
function toggleEditing() {
|
||||
editing.value = !editing.value
|
||||
emit('on-editing', editing.value)
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -17,56 +17,44 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
date: {
|
||||
type: Date,
|
||||
default: new Date(),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedDate: this.formatDay(this.date),
|
||||
showDatePicker: false,
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
date: { type: Date, default: () => new Date() },
|
||||
})
|
||||
const emit = defineEmits(['date-selected'])
|
||||
|
||||
function formatDay(date) {
|
||||
const options = { weekday: 'long', day: 'numeric', month: 'numeric' }
|
||||
return date.toLocaleDateString('en-AU', options)
|
||||
}
|
||||
|
||||
const selectedDate = ref(formatDay(props.date))
|
||||
const showDatePicker = ref(false)
|
||||
|
||||
watch(
|
||||
() => props.date,
|
||||
(newDate) => {
|
||||
selectedDate.value = formatDay(newDate)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
date(newDate) {
|
||||
this.selectedDate = this.formatDay(newDate)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
days() {
|
||||
)
|
||||
|
||||
const days = computed(() => {
|
||||
const today = new Date()
|
||||
const days = []
|
||||
|
||||
const result = []
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const date = new Date(today)
|
||||
date.setDate(today.getDate() + i)
|
||||
days.push(date)
|
||||
const d = new Date(today)
|
||||
d.setDate(today.getDate() + i)
|
||||
result.push(d)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
return days
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatDay(date) {
|
||||
const options = { weekday: 'long', day: 'numeric', month: 'numeric' }
|
||||
return date.toLocaleDateString('en-AU', options)
|
||||
},
|
||||
selectDate(date) {
|
||||
this.selectedDate = this.formatSelectedDate(date)
|
||||
this.showDatePicker = false
|
||||
|
||||
// Emit custom event
|
||||
this.$emit('date-selected', date)
|
||||
},
|
||||
formatSelectedDate(date) {
|
||||
const options = { weekday: 'long', day: 'numeric', month: 'numeric' }
|
||||
return date.toLocaleDateString('en-AU', options)
|
||||
},
|
||||
},
|
||||
function selectDate(date) {
|
||||
selectedDate.value = formatDay(date)
|
||||
showDatePicker.value = false
|
||||
emit('date-selected', date)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<span class="person-list">
|
||||
<span v-for="person in people" :key="person.id">
|
||||
<button class="person-circle remove-person" @click="$emit('remove-person', person)">
|
||||
<button class="person-circle remove-person" @click="removePerson(person)">
|
||||
{{ person.name }}
|
||||
</button>
|
||||
</span>
|
||||
|
|
@ -117,71 +117,63 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { searchPerson } from '@/api/persons'
|
||||
|
||||
export default {
|
||||
name: 'PersonList',
|
||||
props: {
|
||||
people: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAddingPerson: false,
|
||||
searchName: '',
|
||||
searchResults: [],
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const searchNameInput = ref(null)
|
||||
const persondroplist = ref(null)
|
||||
return { searchNameInput, persondroplist }
|
||||
},
|
||||
watch: {
|
||||
searchName: async function () {
|
||||
await this.updateSearchResults()
|
||||
},
|
||||
searchNameInput: async function () {
|
||||
this.searchNameInput?.focus()
|
||||
await this.updateSearchResults()
|
||||
},
|
||||
persondroplist: function () {
|
||||
if (this.persondroplist && this.searchNameInput) {
|
||||
// Align the droplist to the input field & its size
|
||||
const inputRect = this.searchNameInput.getBoundingClientRect()
|
||||
this.persondroplist.style.left = `${inputRect.left}px`
|
||||
this.persondroplist.style.top = `${inputRect.bottom}px`
|
||||
this.persondroplist.style.width = `${inputRect.width}px`
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async updateSearchResults() {
|
||||
const results = await searchPerson(this.searchName)
|
||||
// Exclude people already in the list
|
||||
const idSet = new Set(this.people.map((p) => p.id))
|
||||
this.searchResults = results.filter((p) => !idSet.has(p.id))
|
||||
},
|
||||
addPerson(person) {
|
||||
if (!person && this.searchResults.length > 0) {
|
||||
person = this.searchResults[0]
|
||||
}
|
||||
const props = defineProps({
|
||||
people: { type: Array, default: () => [] },
|
||||
})
|
||||
const emit = defineEmits(['add-person', 'remove-person'])
|
||||
|
||||
if (person?.id >= 0 && !this.people.find((p) => p.id === person.id)) {
|
||||
this.$emit('add-person', person)
|
||||
}
|
||||
const isAddingPerson = ref(false)
|
||||
const searchName = ref('')
|
||||
const searchResults = ref([])
|
||||
|
||||
this.searchName = ''
|
||||
this.searchResults = []
|
||||
this.isAddingPerson = false
|
||||
},
|
||||
removePerson(person) {
|
||||
this.$emit('remove-person', person)
|
||||
},
|
||||
},
|
||||
// Template refs for DOM elements
|
||||
const searchNameInput = ref(null)
|
||||
const persondroplist = ref(null)
|
||||
|
||||
async function updateSearchResults() {
|
||||
const results = await searchPerson(searchName.value)
|
||||
const idSet = new Set(props.people.map((p) => p.id))
|
||||
searchResults.value = results.filter((p) => !idSet.has(p.id))
|
||||
}
|
||||
|
||||
function addPerson(person) {
|
||||
if (!person && searchResults.value.length > 0) {
|
||||
person = searchResults.value[0]
|
||||
}
|
||||
if (person?.id >= 0 && !props.people.find((p) => p.id === person.id)) {
|
||||
emit('add-person', person)
|
||||
}
|
||||
searchName.value = ''
|
||||
searchResults.value = []
|
||||
isAddingPerson.value = false
|
||||
}
|
||||
|
||||
function removePerson(person) {
|
||||
emit('remove-person', person)
|
||||
}
|
||||
|
||||
// Watchers
|
||||
watch(searchName, async () => {
|
||||
await updateSearchResults()
|
||||
})
|
||||
|
||||
watch(searchNameInput, async (el) => {
|
||||
if (el) {
|
||||
el.focus()
|
||||
await updateSearchResults()
|
||||
}
|
||||
})
|
||||
|
||||
watch([persondroplist, searchNameInput], ([drop, input]) => {
|
||||
if (drop && input) {
|
||||
const inputRect = input.getBoundingClientRect()
|
||||
drop.style.left = `${inputRect.left}px`
|
||||
drop.style.top = `${inputRect.bottom}px`
|
||||
drop.style.width = `${inputRect.width}px`
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue