munch-ease-frontend/src/components/meals/PersonList.vue

188 lines
4.2 KiB
Vue
Raw Normal View History

2024-05-04 04:22:37 +00:00
<template>
2025-10-18 02:08:22 +00:00
<span class="person-list">
2024-05-04 04:22:37 +00:00
<span v-for="person in people" :key="person.id">
2025-10-18 02:08:22 +00:00
<button class="person-circle remove-person" @click="$emit('remove-person', person)">
{{ person.name }}
</button>
2024-05-04 04:22:37 +00:00
</span>
<span>
2025-10-18 02:08:22 +00:00
<button
v-if="!isAddingPerson"
class="person-circle add-person"
@click="isAddingPerson = true"
>
+
</button>
<input
v-else
v-model="searchName"
ref="searchNameInput"
@keyup.enter="addPerson"
@keyup.esc="isAddingPerson = false"
@blur="isAddingPerson = false"
/>
<ul
class="person-droplist"
ref="persondroplist"
v-if="isAddingPerson && searchResults.length"
>
<li v-for="person in searchResults" :key="person.id">
<button class="person-circle add-person" @mousedown="addPerson(person)">
{{ person.name }}
</button>
</li>
</ul>
2024-05-04 04:22:37 +00:00
</span>
2025-10-18 02:08:22 +00:00
</span>
2024-05-04 04:22:37 +00:00
</template>
<style scoped>
.person-list {
2025-10-18 02:08:22 +00:00
display: inline-block;
text-align: center;
min-height: 50px;
padding: 0;
margin: 0;
2024-05-04 04:22:37 +00:00
}
/* Remove all the button styling */
.person-circle {
2025-10-18 02:08:22 +00:00
background: none;
border: none;
padding: 0;
margin: 0;
2024-05-04 04:22:37 +00:00
}
/* Show the initials of the person in a circle */
.person-circle {
2025-10-18 02:08:22 +00:00
display: inline-block;
width: 50px;
height: 50px;
line-height: 50px;
text-align: center;
border-radius: 50%;
background: #eee;
2024-05-04 04:22:37 +00:00
}
/* On hover, prompt the user to click to remove the person */
/* change opacity of circle, and use css to add a large cross over the circle */
.remove-person:hover {
2025-10-18 02:08:22 +00:00
cursor: pointer;
position: relative;
background-color: lightcoral;
2024-05-04 04:22:37 +00:00
}
/* Add a cross to the circle */
2025-10-18 02:08:22 +00:00
.remove-person:hover::before,
.remove-person:hover::after {
pointer-events: none;
content: 'X';
color: white;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
2024-05-04 04:22:37 +00:00
}
.add-person {
2025-10-18 02:08:22 +00:00
background-color: lightgreen;
2024-05-04 04:22:37 +00:00
}
.add-person:hover {
2025-10-18 02:08:22 +00:00
cursor: pointer;
font-weight: bolder;
color: white;
background-color: green;
2024-05-04 04:22:37 +00:00
}
.person-droplist {
2025-10-18 02:08:22 +00:00
position: absolute;
background-color: white;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
list-style-type: none;
padding: 0;
margin: 0;
max-height: 40vh;
overflow-y: scroll;
z-index: 1000;
2024-05-04 04:22:37 +00:00
}
.person-droplist li {
2025-10-18 02:08:22 +00:00
padding: 8px;
cursor: pointer;
display: inline;
padding: 1ex 1em;
2024-05-04 04:22:37 +00:00
}
</style>
<script>
2025-10-18 02:08:22 +00:00
import { ref } from 'vue'
2025-10-18 01:42:23 +00:00
import { searchPerson } from '@/api/persons'
2024-05-04 04:22:37 +00:00
export default {
2025-10-18 02:08:22 +00:00
name: 'PersonList',
props: {
people: {
type: Array,
default: () => [],
2024-05-04 04:22:37 +00:00
},
2025-10-18 02:08:22 +00:00
},
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()
2024-05-04 04:22:37 +00:00
},
2025-10-18 02:08:22 +00:00
searchNameInput: async function () {
this.searchNameInput?.focus()
await this.updateSearchResults()
2024-05-04 08:00:50 +00:00
},
2025-10-18 02:08:22 +00:00
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`
}
2024-05-04 04:22:37 +00:00
},
2025-10-18 02:08:22 +00:00
},
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]
}
if (person?.id >= 0 && !this.people.find((p) => p.id === person.id)) {
this.$emit('add-person', person)
}
this.searchName = ''
this.searchResults = []
this.isAddingPerson = false
},
removePerson(person) {
this.$emit('remove-person', person)
},
},
2024-05-04 04:22:37 +00:00
}
2025-10-18 02:08:22 +00:00
</script>