Compare commits

...

2 Commits

Author SHA1 Message Date
Pantry Lead Agent
7d35a3e7b3 feat: implement scan-to-add flow (#25)
Some checks failed
Deploy to Coolify / Run Tests (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Development (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Production (pull_request) Has been cancelled
Deploy to Coolify / Deploy to Test (pull_request) Has been cancelled
Pull Request Checks / Validate PR (pull_request) Has been cancelled
Deploy to Coolify / Code Quality (pull_request) Has been cancelled
- Create useProductLookup composable
- Integrate real product lookup in scan page
- Add query parameter handling in index.vue
- Pre-fill AddItemForm from scan data
- Parse quantity and unit from product data
- Include barcode and brand in notes

Complete end-to-end scan workflow:
1. Scan barcode
2. Fetch from Open Food Facts
3. Navigate to inventory with data
4. Pre-filled add form
5. One-click add to inventory

Closes #25
2026-02-24 00:04:10 +00:00
670b2f9200 Merge pull request 'feat: add product-lookup Edge Function (#24)' (#50) from feature/issue-24-product-lookup into develop
Some checks failed
Deploy to Coolify / Code Quality (push) Has been cancelled
Deploy to Coolify / Run Tests (push) Has been cancelled
Deploy to Coolify / Deploy to Development (push) Has been cancelled
Deploy to Coolify / Deploy to Production (push) Has been cancelled
Deploy to Coolify / Deploy to Test (push) Has been cancelled
2026-02-24 00:03:04 +00:00
4 changed files with 151 additions and 32 deletions

View File

@@ -131,6 +131,16 @@ const { addInventoryItem, addItemTags } = useInventory()
const { getUnits } = useUnits()
const { getTags } = useTags()
const props = defineProps<{
initialData?: {
barcode?: string
name?: string
brand?: string
image_url?: string
quantity?: string
}
}>()
const emit = defineEmits<{
close: []
added: [item: any]
@@ -166,6 +176,40 @@ onMounted(async () => {
if (defaultUnit) {
form.unit_id = defaultUnit.id
}
// Pre-fill from initial data (scan-to-add flow)
if (props.initialData) {
if (props.initialData.name) {
form.name = props.initialData.name
}
// Add brand to notes if available
if (props.initialData.brand) {
form.notes = `Brand: ${props.initialData.brand}`
if (props.initialData.barcode) {
form.notes += `\nBarcode: ${props.initialData.barcode}`
}
} else if (props.initialData.barcode) {
form.notes = `Barcode: ${props.initialData.barcode}`
}
// Parse quantity if available (e.g., "750g")
if (props.initialData.quantity) {
const quantityMatch = props.initialData.quantity.match(/^([\d.]+)\s*([a-zA-Z]+)$/)
if (quantityMatch) {
form.quantity = parseFloat(quantityMatch[1])
// Try to match unit
const unitAbbr = quantityMatch[2].toLowerCase()
const matchedUnit = units.value.find(u =>
u.abbreviation.toLowerCase() === unitAbbr
)
if (matchedUnit) {
form.unit_id = matchedUnit.id
}
}
}
}
})
// Unit options for select

View File

@@ -0,0 +1,61 @@
// Composable for product lookup via Edge Function
export interface ProductData {
barcode: string
name: string
brand?: string
quantity?: string
image_url?: string
category?: string
cached?: boolean
}
export const useProductLookup = () => {
const supabase = useSupabaseClient()
const isLoading = ref(false)
const error = ref<string | null>(null)
const lookupProduct = async (barcode: string): Promise<ProductData | null> => {
isLoading.value = true
error.value = null
try {
const { data, error: functionError } = await supabase.functions.invoke('product-lookup', {
body: { barcode }
})
if (functionError) {
console.error('Product lookup error:', functionError)
error.value = functionError.message || 'Failed to lookup product'
// Return basic product data even on error
return {
barcode,
name: `Product ${barcode}`,
cached: false
}
}
return data as ProductData
} catch (err) {
console.error('Unexpected error during product lookup:', err)
error.value = err instanceof Error ? err.message : 'Unknown error'
// Return basic product data even on error
return {
barcode,
name: `Product ${barcode}`,
cached: false
}
} finally {
isLoading.value = false
}
}
return {
lookupProduct,
isLoading: readonly(isLoading),
error: readonly(error)
}
}

View File

@@ -28,7 +28,8 @@
<div v-if="showAddForm" class="fixed inset-0 z-50 flex items-start justify-center pt-20 px-4 bg-black/50">
<div class="w-full max-w-lg">
<AddItemForm
@close="showAddForm = false"
:initial-data="prefilledData"
@close="handleCloseAddForm"
@added="handleItemAdded"
/>
</div>
@@ -56,13 +57,42 @@ definePageMeta({
layout: 'default'
})
const route = useRoute()
const router = useRouter()
const showAddForm = ref(false)
const editingItem = ref<any>(null)
const refreshKey = ref(0)
const inventoryListRef = ref()
const prefilledData = ref<any>(null)
// Handle scan-to-add flow (Issue #25)
onMounted(() => {
if (route.query.action === 'add') {
// Pre-fill data from query params (from scan)
prefilledData.value = {
barcode: route.query.barcode as string || undefined,
name: route.query.name as string || undefined,
brand: route.query.brand as string || undefined,
image_url: route.query.image_url as string || undefined,
quantity: route.query.quantity as string || undefined,
}
showAddForm.value = true
// Clean up URL
router.replace({ query: {} })
}
})
const handleCloseAddForm = () => {
showAddForm.value = false
prefilledData.value = null
}
const handleItemAdded = (item: any) => {
showAddForm.value = false
prefilledData.value = null
// Reload the inventory list
inventoryListRef.value?.reload()
}

View File

@@ -72,49 +72,33 @@ definePageMeta({
const scannedBarcode = ref<string | null>(null)
const productData = ref<any>(null)
const isLookingUp = ref(false)
const lookupError = ref<string | null>(null)
const showManualEntry = ref(false)
// Use product lookup composable
const { lookupProduct, isLoading: isLookingUp, error: lookupError } = useProductLookup()
const handleBarcodeDetected = async (barcode: string) => {
scannedBarcode.value = barcode
lookupError.value = null
isLookingUp.value = true
try {
// TODO: Implement product lookup via Edge Function (Issue #24)
// For now, create a basic product object
await new Promise(resolve => setTimeout(resolve, 1000)) // Simulate API call
// Fetch product data from Edge Function
const data = await lookupProduct(barcode)
productData.value = {
name: `Product ${barcode}`,
brand: 'Unknown Brand',
barcode: barcode,
image_url: null
}
lookupError.value = 'Product lookup not yet implemented. Using default data.'
} catch (error) {
console.error('Product lookup error:', error)
lookupError.value = 'Failed to look up product. You can still add it manually.'
productData.value = {
name: `Product ${barcode}`,
barcode: barcode
}
} finally {
isLookingUp.value = false
if (data) {
productData.value = data
}
}
const addToInventory = () => {
// TODO: Implement scan-to-add flow (Issue #25)
// Navigate to add form with pre-filled data
// Navigate to home page with add form open and pre-filled
navigateTo({
path: '/',
query: {
action: 'add',
barcode: scannedBarcode.value,
name: productData.value?.name,
brand: productData.value?.brand
name: productData.value?.name || undefined,
brand: productData.value?.brand || undefined,
image_url: productData.value?.image_url || undefined,
quantity: productData.value?.quantity || undefined
}
})
}