import React, { useEffect, useRef, useState } from 'react'; import { Image, KeyboardAvoidingView, Modal, Platform, Pressable, ScrollView, Text, TextInput, View } from 'react-native'; import Ionicons from '@expo/vector-icons/Ionicons'; import { ITEM_PLACEMENTS, ITEM_STATUSES } from '../constants'; import Field from '../components/Field'; import { styles } from '../styles'; import { cn } from '../utils/cn'; const CATEGORY_OPTIONS = ['toiletries', 'electronics', 'documents', 'outfits', 'accessories', 'other']; const PRESET_CATEGORIES = CATEGORY_OPTIONS.filter((option) => option !== 'other'); const IMAGE_QUALITY_OPTIONS = ['low', 'balanced', 'high']; function normalizeValue(value) { return (value || '').trim().toLowerCase(); } function optionLabel(value) { return (value || '').replace(/-/g, ' '); } function SelectField({ value, placeholder, onPress }) { return ( {value ? optionLabel(value) : placeholder} ); } function SelectPopupModal({ visible, title, options, value, onSelect, onClose }) { if (!visible) return null; return ( {title} Close {options.map((option, index) => { const active = value === option; const isLast = index === options.length - 1; return ( onSelect(option)} > {optionLabel(option)} ); })} ); } function qualityValue(level) { if (level === 'high') return 0.95; if (level === 'low') return 0.45; return 0.75; } export default function ItemModal({ visible, itemForm, previousCustomPlacements, setItemModalVisible, updateItemForm, pickItemImage, takeItemImage, saveItemFromModal, }) { const [categoryCustomMode, setCategoryCustomMode] = useState(false); const [placementCustomMode, setPlacementCustomMode] = useState(false); const [openPicker, setOpenPicker] = useState(null); const nameInputRef = useRef(null); const quantityInputRef = useRef(null); const descriptionInputRef = useRef(null); const lentToInputRef = useRef(null); useEffect(() => { if (!visible) { setOpenPicker(null); return; } const normalizedCategory = normalizeValue(itemForm.category); const customCategory = !!normalizedCategory && !PRESET_CATEGORIES.includes(normalizedCategory); setCategoryCustomMode(customCategory); setPlacementCustomMode(itemForm.placement === 'other'); setOpenPicker(null); }, [visible, itemForm.id]); useEffect(() => { if (!visible || itemForm.id) return undefined; const timeout = setTimeout(() => { nameInputRef.current?.focus?.(); }, 80); return () => clearTimeout(timeout); }, [visible, itemForm.id]); const mediaOptions = { quality: qualityValue(itemForm.imageQuality), allowCrop: !!itemForm.imageAllowCrop, }; const normalizedCategory = normalizeValue(itemForm.category); const categorySelectValue = PRESET_CATEGORIES.includes(normalizedCategory) ? normalizedCategory : ''; const savedPlacementOptions = (previousCustomPlacements || []).slice(0, 6); const savedPlacementValue = savedPlacementOptions.includes(itemForm.placementCustom) ? itemForm.placementCustom : ''; const pickerConfig = { category: { title: 'Select category', options: CATEGORY_OPTIONS, value: categorySelectValue, onSelect: chooseCategory, }, status: { title: 'Select status', options: ITEM_STATUSES, value: itemForm.status, onSelect: chooseStatus, }, placement: { title: 'Select placement', options: ITEM_PLACEMENTS, value: itemForm.placement, onSelect: choosePlacement, }, savedPlacement: { title: 'Saved custom locations', options: savedPlacementOptions, value: savedPlacementValue, onSelect: chooseSavedPlacement, }, imageQuality: { title: 'Image quality', options: IMAGE_QUALITY_OPTIONS, value: itemForm.imageQuality, onSelect: chooseImageQuality, }, }; const activePicker = openPicker ? pickerConfig[openPicker] : null; function chooseCategory(value) { if (value === 'other') { setCategoryCustomMode(true); if (PRESET_CATEGORIES.includes(normalizedCategory)) { updateItemForm('category', ''); } setOpenPicker(null); return; } setCategoryCustomMode(false); updateItemForm('category', value); setOpenPicker(null); } function chooseStatus(value) { updateItemForm('status', value); setOpenPicker(null); } function choosePlacement(value) { if (value === 'other') { setPlacementCustomMode(true); updateItemForm('placement', 'other'); setOpenPicker(null); return; } setPlacementCustomMode(false); updateItemForm('placement', value); setOpenPicker(null); } function chooseSavedPlacement(value) { updateItemForm('placementCustom', value); setOpenPicker(null); } function chooseImageQuality(value) { updateItemForm('imageQuality', value); setOpenPicker(null); } function resetCategorySelector() { setCategoryCustomMode(false); updateItemForm('category', ''); setOpenPicker(null); } function resetPlacementSelector() { setPlacementCustomMode(false); updateItemForm('placement', ITEM_PLACEMENTS[0] || 'suitcase'); setOpenPicker(null); } return ( {itemForm.id ? 'Update Item' : 'Add Item'} setItemModalVisible(false)}> Close updateItemForm('name', v)} placeholder="Toothbrush" placeholderTextColor="#71717a" returnKeyType="next" blurOnSubmit={false} onSubmitEditing={() => quantityInputRef.current?.focus?.()} /> { const numeric = (v || '').replace(/[^0-9]/g, ''); updateItemForm('quantity', numeric ? Math.max(1, Number.parseInt(numeric, 10)) : 1); }} placeholder="1" placeholderTextColor="#71717a" keyboardType="number-pad" returnKeyType="next" blurOnSubmit={false} onSubmitEditing={() => descriptionInputRef.current?.focus?.()} /> updateItemForm('description', v)} placeholder="Optional" placeholderTextColor="#71717a" returnKeyType={itemForm.status === 'lent-to' ? 'next' : 'done'} onSubmitEditing={() => { if (itemForm.status === 'lent-to') { lentToInputRef.current?.focus?.(); } }} /> {categoryCustomMode ? ( updateItemForm('category', v)} placeholder="Custom category" placeholderTextColor="#71717a" /> ) : ( setOpenPicker('category')} /> )} setOpenPicker('status')} /> {placementCustomMode ? ( updateItemForm('placementCustom', v)} placeholder="bath-kit" placeholderTextColor="#71717a" /> Custom location {savedPlacementOptions.length ? ( setOpenPicker('savedPlacement')} /> ) : null} ) : ( setOpenPicker('placement')} /> )} {itemForm.status === 'lent-to' ? ( updateItemForm('lentTo', v)} placeholder="Person name" placeholderTextColor="#71717a" returnKeyType="done" /> ) : null} takeItemImage(mediaOptions)}> Take photo pickItemImage(mediaOptions)}> {itemForm.imageUri ? 'From gallery (change)' : 'From gallery'} {!!itemForm.imageUri ? ( <> setOpenPicker('imageQuality')} /> updateItemForm('imageAllowCrop', !itemForm.imageAllowCrop)}> Enable optional crop before save ) : null} {itemForm.id ? 'Save Changes' : 'Add Item'} activePicker?.onSelect?.(value)} onClose={() => setOpenPicker(null)} /> ); }