import React, { useState, useMemo } from 'react'; import { View, Text, FlatList, TextInput, TouchableOpacity, StyleSheet, Modal, ScrollView, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { router } from 'expo-router'; import { MaterialIcons } from '@expo/vector-icons'; import { useHouseholdStore } from '../../src/hooks/useHousehold'; import { ItemCard } from '../../src/components/inventory/ItemCard'; import { updateItemQuantity } from '../../src/services/items'; import { COLORS, CATEGORY_LABELS, BASE_CATEGORIES } from '../../src/constants'; import { useCustomOptions } from '../../src/hooks/useCustomOptions'; import { isExpiringSoon, isExpired } from '../../src/utils'; import Toast from 'react-native-toast-message'; type SortKey = 'name_asc' | 'name_desc' | 'qty_asc' | 'qty_desc' | 'expiry_asc' | 'added_desc'; type MhdFilter = 'all' | 'expiring' | 'expired'; const SORT_OPTIONS: { key: SortKey; label: string }[] = [ { key: 'name_asc', label: 'Name A–Z' }, { key: 'name_desc', label: 'Name Z–A' }, { key: 'qty_asc', label: 'Menge ↑' }, { key: 'qty_desc', label: 'Menge ↓' }, { key: 'expiry_asc', label: 'MHD ↑' }, { key: 'added_desc', label: 'Neueste' }, ]; const MHD_OPTIONS: { key: MhdFilter; label: string }[] = [ { key: 'all', label: 'Alle' }, { key: 'expiring', label: 'Bald ablaufend' }, { key: 'expired', label: 'Abgelaufen' }, ]; export default function InventoryScreen() { const items = useHouseholdStore((s) => s.items); const household = useHouseholdStore((s) => s.household); const deviceId = useHouseholdStore((s) => s.deviceId); const [search, setSearch] = useState(''); const [filterVisible, setFilterVisible] = useState(false); const [selectedCategories, setSelectedCategories] = useState([]); const [selectedLocations, setSelectedLocations] = useState([]); const [mhdFilter, setMhdFilter] = useState('all'); const [sortKey, setSortKey] = useState('name_asc'); const { options: categoryOptions } = useCustomOptions('houseorg_custom_categories', [...BASE_CATEGORIES]); const availableLocations = useMemo( () => Array.from(new Set(items.map((i) => i.storageLocation).filter(Boolean))).sort() as string[], [items] ); const activeFilterCount = selectedCategories.length + selectedLocations.length + (mhdFilter !== 'all' ? 1 : 0) + (sortKey !== 'name_asc' ? 1 : 0); const filtered = useMemo(() => { const term = search.toLowerCase(); let result = items.filter((item) => { if (term && !item.name.toLowerCase().includes(term)) return false; if (selectedCategories.length > 0 && !selectedCategories.includes(item.category)) return false; if (selectedLocations.length > 0 && !selectedLocations.includes(item.storageLocation)) return false; if (mhdFilter === 'expiring' && !isExpiringSoon(item.expiryDate)) return false; if (mhdFilter === 'expired' && !isExpired(item.expiryDate)) return false; return true; }); return [...result].sort((a, b) => { switch (sortKey) { case 'name_asc': return a.name.localeCompare(b.name, 'de'); case 'name_desc': return b.name.localeCompare(a.name, 'de'); case 'qty_asc': return a.quantity - b.quantity; case 'qty_desc': return b.quantity - a.quantity; case 'expiry_asc': if (!a.expiryDate && !b.expiryDate) return 0; if (!a.expiryDate) return 1; if (!b.expiryDate) return -1; return a.expiryDate.getTime() - b.expiryDate.getTime(); case 'added_desc': return b.createdAt.getTime() - a.createdAt.getTime(); default: return 0; } }); }, [items, search, selectedCategories, selectedLocations, mhdFilter, sortKey]); const toggleCategory = (cat: string) => setSelectedCategories((prev) => prev.includes(cat) ? prev.filter((c) => c !== cat) : [...prev, cat] ); const toggleLocation = (loc: string) => setSelectedLocations((prev) => prev.includes(loc) ? prev.filter((l) => l !== loc) : [...prev, loc] ); const resetFilters = () => { setSelectedCategories([]); setSelectedLocations([]); setMhdFilter('all'); setSortKey('name_asc'); }; const handleQuantityChange = async (itemId: string, qty: number) => { if (!household) return; try { await updateItemQuantity(household.id, itemId, qty); } catch { Toast.show({ type: 'error', text1: 'Fehler beim Speichern' }); } }; const activeSummary = [ selectedCategories.length > 0 && `${selectedCategories.length} Kategorie${selectedCategories.length > 1 ? 'n' : ''}`, selectedLocations.length > 0 && `${selectedLocations.length} Ort${selectedLocations.length > 1 ? 'e' : ''}`, mhdFilter !== 'all' && (mhdFilter === 'expiring' ? 'Bald ablaufend' : 'Abgelaufen'), sortKey !== 'name_asc' && SORT_OPTIONS.find((s) => s.key === sortKey)?.label, ] .filter(Boolean) .join(' · '); return ( 0 && styles.filterBtnActive]} onPress={() => setFilterVisible(true)} accessibilityLabel="Filter" > 0 ? COLORS.white : COLORS.text} /> {activeFilterCount > 0 && ( {activeFilterCount} )} router.push({ pathname: '/modals/add-item', params: { householdId: household?.id ?? '', deviceId: deviceId ?? '' }, }) } accessibilityLabel="Artikel hinzufügen" > {activeFilterCount > 0 && ( {activeSummary} Zurücksetzen )} item.id} renderItem={({ item }) => ( router.push({ pathname: '/modals/item-detail', params: { itemId: item.id } }) } onQuantityChange={(qty) => handleQuantityChange(item.id, qty)} /> )} contentContainerStyle={[styles.list, filtered.length === 0 && styles.listEmpty]} ListEmptyComponent={ 0 ? 'search-off' : 'kitchen'} size={36} color={COLORS.primaryLight} /> {search || activeFilterCount > 0 ? 'Keine Treffer' : 'Noch leer'} {search || activeFilterCount > 0 ? 'Filter oder Suche anpassen.' : 'Tippe auf + und fotografiere deinen ersten Artikel.'} } /> setFilterVisible(false)} /> Filter & Sortierung Zurücksetzen {SORT_OPTIONS.map(({ key, label }) => ( setSortKey(key)} > {label} ))} {categoryOptions.map((cat) => ( toggleCategory(cat)} > {CATEGORY_LABELS[cat] ?? cat} ))} {availableLocations.length > 0 && ( {availableLocations.map((loc) => ( toggleLocation(loc)} > {loc} ))} )} {MHD_OPTIONS.map(({ key, label }) => ( setMhdFilter(key)} > {label} ))} setFilterVisible(false)}> {filtered.length === 0 ? 'Keine Treffer' : `${filtered.length} Artikel anzeigen`} ); } function FilterSection({ title, children }: { title: string; children: React.ReactNode }) { return ( {title} {children} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: COLORS.surface }, searchRow: { flexDirection: 'row', gap: 10, paddingHorizontal: 16, paddingTop: 12, paddingBottom: 8, }, searchBox: { flex: 1, flexDirection: 'row', alignItems: 'center', backgroundColor: COLORS.white, borderRadius: 12, paddingHorizontal: 12, gap: 8, height: 46, shadowColor: '#000', shadowOpacity: 0.04, shadowRadius: 4, elevation: 1, }, searchInput: { flex: 1, fontSize: 15, color: COLORS.text }, filterBtn: { width: 46, height: 46, borderRadius: 14, backgroundColor: COLORS.white, justifyContent: 'center', alignItems: 'center', shadowColor: '#000', shadowOpacity: 0.04, shadowRadius: 4, elevation: 1, }, filterBtnActive: { backgroundColor: COLORS.primary }, filterBadge: { position: 'absolute', top: -4, right: -4, width: 18, height: 18, borderRadius: 9, backgroundColor: COLORS.danger, justifyContent: 'center', alignItems: 'center', }, filterBadgeText: { fontSize: 10, fontWeight: '700', color: COLORS.white }, addBtn: { width: 46, height: 46, borderRadius: 14, backgroundColor: COLORS.primary, justifyContent: 'center', alignItems: 'center', shadowColor: COLORS.primary, shadowOpacity: 0.35, shadowRadius: 8, shadowOffset: { width: 0, height: 3 }, elevation: 4, }, activeFilterBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingBottom: 6, gap: 8, }, activeFilterText: { fontSize: 12, color: COLORS.textSecondary, flex: 1 }, resetText: { fontSize: 12, color: COLORS.primary, fontWeight: '600' }, list: { paddingVertical: 8, paddingBottom: 32 }, listEmpty: { flex: 1 }, empty: { flex: 1, paddingTop: 80, alignItems: 'center', gap: 10, paddingHorizontal: 32 }, emptyIconWrap: { width: 72, height: 72, borderRadius: 24, backgroundColor: '#E8F5E9', justifyContent: 'center', alignItems: 'center', marginBottom: 4, }, emptyTitle: { fontSize: 17, fontWeight: '600', color: COLORS.text }, emptyText: { color: COLORS.textSecondary, textAlign: 'center', fontSize: 14, lineHeight: 20 }, // Modal modalContainer: { flex: 1, justifyContent: 'flex-end' }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)' }, filterSheet: { backgroundColor: COLORS.white, borderTopLeftRadius: 24, borderTopRightRadius: 24, maxHeight: '78%', paddingTop: 12, }, filterHandle: { width: 36, height: 4, borderRadius: 2, backgroundColor: COLORS.border, alignSelf: 'center', marginBottom: 16, }, filterHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, marginBottom: 4, }, filterTitle: { fontSize: 17, fontWeight: '700', color: COLORS.text }, filterScrollContent: { paddingHorizontal: 20, paddingBottom: 8 }, filterSection: { marginTop: 22 }, filterSectionTitle: { fontSize: 11, fontWeight: '600', color: COLORS.textSecondary, textTransform: 'uppercase', letterSpacing: 0.7, marginBottom: 10, }, chipWrap: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }, chip: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 20, backgroundColor: COLORS.surface, borderWidth: 1, borderColor: COLORS.border, }, chipActive: { backgroundColor: COLORS.primary, borderColor: COLORS.primary }, chipText: { fontSize: 13, color: COLORS.text, fontWeight: '500' }, chipTextActive: { color: COLORS.white, fontWeight: '600' }, applyBtn: { margin: 16, marginTop: 12, backgroundColor: COLORS.primary, borderRadius: 14, paddingVertical: 16, alignItems: 'center', shadowColor: COLORS.primary, shadowOpacity: 0.28, shadowRadius: 8, shadowOffset: { width: 0, height: 3 }, elevation: 4, }, applyBtnText: { color: COLORS.white, fontSize: 16, fontWeight: '600' }, });