feat!: remove Person entity usage, adopt MemberRef across meals; update SDK and UI
This commit is contained in:
parent
166dc04948
commit
a9f2b6f5a8
7 changed files with 39 additions and 103 deletions
|
|
@ -224,29 +224,7 @@ export async function parseProduct(
|
|||
return (await res.json()) ?? null
|
||||
}
|
||||
|
||||
// Persons
|
||||
async function listPersons(params?: { q?: string | null; cursor?: string | null; limit?: number }): Promise<Page<components['schemas']['Person']>> {
|
||||
const query: Record<string, unknown> = {}
|
||||
if (params) {
|
||||
if (params.q !== undefined) query.q = params.q
|
||||
if (params.cursor !== undefined) query.cursor = params.cursor
|
||||
if (typeof params.limit === 'number') query.limit = params.limit
|
||||
}
|
||||
const url = '/api/v1/persons' + (Object.keys(query).length ? ('?' + new URLSearchParams(query as Record<string, string>)) : '')
|
||||
const res = await fetchApi(url, { method: 'GET' })
|
||||
if (!res.ok) throw httpError(res, null)
|
||||
const data = await res.json()
|
||||
const normalized = Array.isArray(data) ? { items: data } : (data ?? null)
|
||||
return fromOpenApiPage<components['schemas']['Person'], components['schemas']['Person']>(normalized, (p) => p)
|
||||
}
|
||||
|
||||
export async function getPersonsInHome(): Promise<Page<components['schemas']['Person']>> {
|
||||
return listPersons()
|
||||
}
|
||||
|
||||
export async function searchPersons(name: string): Promise<Page<components['schemas']['Person']>> {
|
||||
return listPersons({ q: name })
|
||||
}
|
||||
// Person-related functions are removed as the entity is no longer in use.
|
||||
|
||||
// Meals
|
||||
export async function getUpcomingMeals(from: Date, to: Date): Promise<Meal[]> {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const router = useRouter()
|
|||
const route = useRoute()
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const isMultitenant = computed(() => typeof process !== 'undefined' && process.env?.VUE_APP_MULTITENANT_ENABLED === 'true')
|
||||
const isMultitenant = computed(() => true)
|
||||
const { user } = useAuth()
|
||||
|
||||
onMounted(async () => {})
|
||||
|
|
|
|||
|
|
@ -7,21 +7,21 @@
|
|||
/>
|
||||
<div class="persons-list">
|
||||
Cooked by
|
||||
<person-list
|
||||
<member-list
|
||||
:people="meal.chefs"
|
||||
@remove-person="(p) => removePerson('chefs', p)"
|
||||
@add-person="(p) => addPerson('chefs', p)"
|
||||
@remove="(p) => removePerson('chefs', p)"
|
||||
@add="(p) => addPerson('chefs', p)"
|
||||
/>
|
||||
for
|
||||
<person-list
|
||||
<member-list
|
||||
:people="meal.consumers"
|
||||
@remove-person="(p) => removePerson('consumers', p)"
|
||||
@add-person="(p) => addPerson('consumers', p)"
|
||||
@remove="(p) => removePerson('consumers', p)"
|
||||
@add="(p) => addPerson('consumers', p)"
|
||||
/>, with
|
||||
<person-list
|
||||
<member-list
|
||||
:people="meal.cleanup"
|
||||
@remove-person="(p) => removePerson('cleanup', p)"
|
||||
@add-person="(p) => addPerson('cleanup', p)"
|
||||
@remove="(p) => removePerson('cleanup', p)"
|
||||
@add="(p) => addPerson('cleanup', p)"
|
||||
/>
|
||||
on cleanup.
|
||||
</div>
|
||||
|
|
@ -131,11 +131,13 @@ import RecipeSearchBox from '@/components/recipes/RecipeSearchBox.vue'
|
|||
import RecipeCard from '@/components/recipes/RecipeCard.vue'
|
||||
import DatePicker from './DatePicker.vue'
|
||||
import EditableIngredientsPanel from '../ingredients/EditableIngredientsPanel.vue'
|
||||
import PersonList from './PersonList.vue'
|
||||
import MemberList from './MemberList.vue'
|
||||
import CompactParsedIngredient from '../ingredients/CompactParsedIngredient.vue'
|
||||
const showIngredientsIcon = new URL('@/assets/show-ingredients.svg', import.meta.url).toString()
|
||||
const trash = new URL('@/assets/trash.svg', import.meta.url).toString()
|
||||
|
||||
import type { MemberRef } from '@/domain/types'
|
||||
|
||||
function addPersonIfNotExists<T extends { id: number }>(list: T[], person: T | null | undefined) {
|
||||
if (!person) return
|
||||
if (!list.find((p) => p.id === person.id)) {
|
||||
|
|
@ -203,14 +205,12 @@ function updateIngredient(ingredient: Ingredient, newIngredient: Ingredient) {
|
|||
meal.extraIngredients = meal.extraIngredients.map((i) => (i === ingredient ? newIngredient : i))
|
||||
}
|
||||
|
||||
function removePerson(list: PeopleKey, person: { id: number }) {
|
||||
function removePerson(list: PeopleKey, person: MemberRef) {
|
||||
meal[list] = meal[list].filter((p) => p.id !== person.id)
|
||||
}
|
||||
|
||||
function addPerson(list: PeopleKey, person: { id: number; name?: string; displayName?: string }) {
|
||||
// Normalize person into MemberRef shape
|
||||
const normalized = { id: person.id, displayName: (person as any).displayName ?? (person as any).name ?? '' }
|
||||
addPersonIfNotExists(meal[list], normalized)
|
||||
function addPerson(list: PeopleKey, person: MemberRef) {
|
||||
addPersonIfNotExists(meal[list], person)
|
||||
}
|
||||
|
||||
async function selectRecipe(recipe: { id: number | string }) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
class="person-circle remove-person"
|
||||
@click="removePerson(person)"
|
||||
>
|
||||
{{ person.displayName || person.name || '' }}
|
||||
{{ person.displayName || '' }}
|
||||
</button>
|
||||
</span>
|
||||
<span>
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
class="person-circle add-person"
|
||||
@mousedown="addPerson(person)"
|
||||
>
|
||||
{{ person.displayName || person.name || '' }}
|
||||
{{ person.displayName || '' }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -50,36 +50,35 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { searchPersons } from '@/api/sdk'
|
||||
// Accept either legacy Person shape or new MemberRef
|
||||
type PersonLike = { id: number; name?: string; displayName?: string }
|
||||
const props = withDefaults(defineProps<{ people?: PersonLike[] }>(), { people: () => [] })
|
||||
import { listMembers } from '@/api/households'
|
||||
import type { MemberRef } from '@/domain/types'
|
||||
|
||||
const props = withDefaults(defineProps<{ people?: MemberRef[] }>(), { people: () => [] })
|
||||
const emit = defineEmits<{
|
||||
(e: 'add-person', person: PersonLike): void
|
||||
(e: 'remove-person', person: PersonLike): void
|
||||
(e: 'add', person: MemberRef): void
|
||||
(e: 'remove', person: MemberRef): void
|
||||
}>()
|
||||
|
||||
const isAddingPerson = ref(false)
|
||||
const searchName = ref('')
|
||||
const searchResults = ref<PersonLike[]>([])
|
||||
const searchResults = ref<MemberRef[]>([])
|
||||
|
||||
// Template refs for DOM elements
|
||||
const searchNameInput = ref<HTMLInputElement | null>(null)
|
||||
const persondroplist = ref<HTMLUListElement | null>(null)
|
||||
|
||||
async function updateSearchResults() {
|
||||
const q = searchName.value.trim()
|
||||
if (!q) {
|
||||
searchResults.value = []
|
||||
return
|
||||
}
|
||||
const page = await searchPersons(q)
|
||||
const results: PersonLike[] = page.items.map((p) => ({ id: p.id, name: p.name }))
|
||||
const q = searchName.value.trim().toLowerCase()
|
||||
const allMembers = await listMembers()
|
||||
const idSet = new Set(props.people.map((p) => p.id))
|
||||
searchResults.value = results.filter((p: PersonLike) => !idSet.has(p.id))
|
||||
let results: MemberRef[] = allMembers.filter((p) => !idSet.has(p.id))
|
||||
if (q) {
|
||||
results = results.filter((p) => p.displayName.toLowerCase().includes(q))
|
||||
}
|
||||
searchResults.value = results
|
||||
}
|
||||
|
||||
function addPerson(person?: PersonLike) {
|
||||
function addPerson(person?: MemberRef) {
|
||||
if (!person && searchResults.value.length > 0) {
|
||||
person = searchResults.value[0]
|
||||
}
|
||||
|
|
@ -90,15 +89,15 @@ function addPerson(person?: PersonLike) {
|
|||
return
|
||||
}
|
||||
if (!props.people.find((p) => p.id === person.id)) {
|
||||
emit('add-person', person)
|
||||
emit('add', person)
|
||||
}
|
||||
searchName.value = ''
|
||||
searchResults.value = []
|
||||
isAddingPerson.value = false
|
||||
}
|
||||
|
||||
function removePerson(person: PersonLike) {
|
||||
emit('remove-person', person)
|
||||
function removePerson(person: MemberRef) {
|
||||
emit('remove', person)
|
||||
}
|
||||
|
||||
// Watchers
|
||||
|
|
@ -20,7 +20,8 @@ export type Ingredient = components['schemas']['Ingredient']
|
|||
export type Product = components['schemas']['Product']
|
||||
export type User = components['schemas']['User']
|
||||
export type Household = components['schemas']['HouseholdResponse']
|
||||
export type Person = components['schemas']['Person']
|
||||
// Member reference used across meals and lookups
|
||||
export type MemberRef = components['schemas']['MemberRef']
|
||||
// v2 no longer exposes Recipe-Input; use RecipeCreate at boundary when creating
|
||||
export type MealInput = components['schemas']['MealIn']
|
||||
export type MealRecipeOut = components['schemas']['MealRecipe']
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { server, http, HttpResponse } from './test-setup'
|
||||
import { getPersonsInHome } from '@/api/sdk'
|
||||
|
||||
describe('persons api errors', () => {
|
||||
it('propagates non-2xx errors', async () => {
|
||||
server.use(
|
||||
http.get('*/api/v1/persons', () => new HttpResponse(null, { status: 422 }))
|
||||
)
|
||||
await expect(getPersonsInHome()).rejects.toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { server, http, HttpResponse } from './test-setup'
|
||||
import { getPersonsInHome, searchPersons } from '@/api/sdk'
|
||||
|
||||
// Persons API tests
|
||||
|
||||
describe('persons api (typed client)', () => {
|
||||
it('lists persons in home', async () => {
|
||||
server.use(
|
||||
http.get('*/api/v1/persons', () => {
|
||||
return HttpResponse.json([{ id: 1, name: 'Ada Lovelace' }])
|
||||
})
|
||||
)
|
||||
const page = await getPersonsInHome()
|
||||
expect(Array.isArray(page.items)).toBe(true)
|
||||
expect(page.items[0].name).toBe('Ada Lovelace')
|
||||
})
|
||||
|
||||
it('searches persons by name', async () => {
|
||||
server.use(
|
||||
http.get('*/api/v1/persons', ({ request }) => {
|
||||
const url = new URL(request.url)
|
||||
const q = url.searchParams.get('q')
|
||||
return HttpResponse.json(q ? [{ id: 2, name: 'Alan Turing' }] : [])
|
||||
})
|
||||
)
|
||||
const page = await searchPersons('alan')
|
||||
expect(page.items[0].name.toLowerCase()).toContain('alan')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue